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>
28 KiB
SDD 10 — Roadmap
Estrategia: validar la capa AI-nativa sobre Alpine antes de la distro propia
La innovación de hammer no es la distro — es el substrato AI-nativo (build determinista +
mutable + diario + .swm). Construir bootstrap + init + userland de cero antes de validar esa
capa sería arriesgar meses contra una hipótesis no probada. Por eso:
Fase 0–6: montamos
hammersobre Alpine (musl + busybox + FHS mutable, ya cocidos). Validamos el bucle completo en semanas. Track posterior: una vez probado, bajamos a distro propia (init propio, bootstrap propio, userland propio) reusando todo el tooling sin retrabajo.
Ver ADR 0002.
Fases (sobre Alpine)
Fase 0 — Laboratorio de build ✅
hammer-core: tiposRecipe,ArtifactHash,Store(BLAKE3, layout/store).hammer-build: sandbox conbubblewrap+zig cc→ binario musl estático.- CAS: hashing de entrada, caché por hash, sellado en el store.
hammer build <recipe.toml>→ imprime el hash del artefacto.- Fuente git y tarball (sha256 fijado), patches opcionales.
- Heurística de build system: autotools / cmake / meson / make plano.
- Caché persistente de zig entre builds (~30s/build ahorrados).
Fase 1 — Hidratación ✅
hydrate(hash, target, mode): hardlink a FHS,patchelfpara el caso dinámico.hammer hydrate <hash> --into /.
🎯 Primer entregable (Fase 0 + 1) ✅
GNU grep 3.12 compilado estático desde el tarball upstream, sellado en el store
(b3:b81e893e…), hidratado por hardlink a /usr/bin/grep y ejecutado dentro del rootfs
Alpine vía bwrap — confirma el corazón "fábrica funcional → FHS mutable" sobre el
substrato Alpine. La VM/LXC dedicada queda como ejercicio de empaque, no como
pre-requisito de validación.
Fase 2 — Overlay de experimentación ✅
- Crate
hammer-overlay:try/commit/discard/statussobreoverlayfs. - Manifiesto persistente en
<state_root>/<id>/state.json;statusenumera. commitpromociona archivos y procesa whiteouts (char dev 0/0) como remove.- Subcomandos del CLI:
hammer try [targets…],commit <id>,discard <id>,status. - Tests E2E con bwrap+user-ns, gateados en
HAMMER_OVERLAY_TESTS=1(kernel-dependiente). - Hook al diario en
commit(Fase 3 lo añadió víacommit_with_journal). - Anidación real de overlays. Un
trysobre un target ya cubierto apila un overlay nuevo (el kernel usa el merged view inferior comolowerdir). Orden de apilamiento total y determinista víastack_key(created_at+id, conseqpor proceso enfresh_idpara evitar colisiones en ráfaga).commit/discardexigen LIFO:blocking_overlaysdetecta capas más jóvenes que solapan targets (igualdad o ancestro de path) y la operación falla conError::Shadowedsi las hay. Guard cubierto por 6 unit tests; el stack real (capa 2 ve la 1, LIFO rechaza commit de abajo, commit arriba→abajo fusiona) poroverlay_nested_stack_inside_userns, gated enHAMMER_OVERLAY_TESTS. Pendiente menor: unicidad de orden entre procesos concurrentes (track posterior).
Fase 3 — Diario de mutaciones ✅
- Crate
hammer-journal:MutationEvent,Source(HammerHydrate/HammerCommit/External), append/read/tail/follow JSON-líneas. RFC 3339 sin chrono. hammerdconfanotify(FAN_CLOSE_WRITE) sobre/bin,/sbin,/usr/bin,/usr/sbin,/lib,/usr/lib,/etc. Filtra eventos bajo overlays activos. Fallback graceful sin CAP_SYS_ADMIN.hammer journal [--tail N] [--follow] [--format pretty|json].hammer commitregistra cada archivo promocionado víacommit_with_journal.- Refinar la
opdel watcher.watcher::classify_op(&journal, path, deleted)derivaDelete(el fd resuelve a" (deleted)", que ahora recortamos del path en vez de colarlo al diario),Edit(el diario ya tenía un evento previo no-Deletedel path) oCreate(primera mutación observada, o resurrección tras unDelete). Es la divergencia que el diario atestigua, no la verdad absoluta del FS. La atribución plena víaFAN_REPORT_DFID_NAME(nombre en eventos de directorio,rename-sobre-destino,open_by_handle_at+CAP_DAC_READ_SEARCH) queda para el track posterior:nix0.30 no parsea los info-records de FID y el modo FID perdería el fd que hoy hashea el contenido. - Hash del contenido tras la mutación (
content_hash) y de-dup idempotente.hammer_journal::content_hash_of/hash_file(blake3 plano, estilob3sum, distinto delof_inputsde artefactos).Journal::record_dedupomite el evento si deja el archivo idéntico al último de ese path (o unDeletesobre algo ya borrado);last_for_pathlo resuelve. El watcher hashea cadaCLOSE_WRITEy usarecord_dedup: una reescritura sin cambios ni ensucia el diario ni despierta el bus.
Fase 4 — Formato y flujo .swm ✅
Swmde/serialización YAML estable (roundtrip).verify_schema(estructura + invariantes por mutación) yverify_base(distro_version + pins) conBaseRef/BaseCompat.apply_config_edit(hunks-/+con búsqueda exacta, no-fuzz) yapply_file_drop(base64 inline + verificación BLAKE3 vscontent_hash).- Bridge
Mutation::SourcePatch→Recipe+build+ hidratación. - CLI:
hammer apply [--prefix DIR] [--base-ref base.json],hammer swm-verify,hammer export --journal DIR > out.swm. patch_url/content_urlremotos.hammer_build::download(curl, sólo fuera del sandbox; aceptafile://para tests offline).build_source_patchdescargapatch_urlalswm-recipes/<commit>.patchantes de compilar;hammer applydescargacontent_urly verifica BLAKE3 antes de escribir (hash erróneo ⇒ nada tocado). Su integridad la cubrecontent_hash(file_drop) y el build reproducible +expected_hash(patch).- Provenance en
export: mapa artefacto→receta vía sidecar.hammer/recipe.tomlquehammer-build::buildescribe dentro del artefacto antes de sellar.hammer exportagrupa eventos porartifact_hashy emite UNsource_patchpor grupo cuya receta sea recuperable; lo demás cae al fallbackfile_drop. Sourcegitytarballmodelados:Mutation::SourcePatch(yRecipeInline) llevan camposrepo/commitxortarball/sha256(resueltos porswm::swm_source_kind, misma regla querecipe::Source::kind);hammer exportreconstruye ambos sin caer afile_drop. - Firma
signature(ed25519) yTrustStorelocal.hammer_core::sign:KeyPair(genera/carga/escribe claves),TrustStore::load(dir)(lee*.ed25519.pub),Swm::verify_signature(&trust) → SigStatus(trusted/unknown-key/bad-sig/unsigned) sobre bytes canónicos JSON del manifiesto sin la firma. CLI:hammer keygen,hammer swm-sign, yhammer swm-verify --trust DIR. La firma reporta autoría/integridad; NO autoriza promover (sigue mandando reproducir + commit). SDD 09 §3,§5. - Hecho cuando: exportas un cambio, lo aplicas en otra máquina y reproduce idéntico.
✅ Demostrado en
crates/hammer-cli/tests/swm_roundtrip.rsparaconfig_edit+file_drop.source_patchreusa el camino de Fase 0/1 (gated enHAMMER_NETWORK_TESTS).
Fase 5 — Bus de agente ✅ (queda CRASHED real, diferido al track posterior)
/run/init.control(FIFO humano):mkfifo, reader que loguea cada línea, yhammer ctl <line>que escribe al FIFO con error claro si no existe./run/agent.sock(JSON-líneas) con handshakeHello/Welcome, auth viaSO_PEERCREDyCapsPolicypor UID.- Comandos
Compile/Inject/Query/Init; eventosWelcome/BuildReady/BuildFailed/Injected/QueryResult/InitAck/Modified/Crashed/Error. - EventBus in-process: el watcher publica
Modifiedy todas las conexiones lo reciben. - Subsistemas independientes en
hammerd::main(FIFO/watcher/bus en threads); si uno falla en init, los demás siguen. - Política expresiva leída de
/etc/hammer/agent-caps.toml(hammerd --agent-caps).hammer_core::AgentCapsConfig:default+[[rule]]poruid/gid(primera que casa gana),caps_for(uid,gid).bus::policy_from_configla enchufa; sin fichero o con fichero inválido, cae a la política por-UID por defecto (avisando). Ejemplo enexamples/agent-caps.toml. Eldefault_policyhardcoded queda como fallback. CRASHEDreal (requiere supervisión de servicios, que llega con el init propio del track posterior).BuildFailed.log_tailcon cola real del lab.Sandbox::runhace tee de stdout/stderr (sigue viéndose en vivo) y retiene las últimas 50 líneas; al fallar una fase devuelveBuildFailure { reason, log_tail }envuelto enError::Other. El bus lo recupera porBuildFailure::from_error(downcast) y separareasondel log textual del compilador, para que la IA reaccione al error real, no sólo al mensaje de Rust.- Hecho cuando: un cliente externo dispara un build y recibe el evento de fin por el
socket. ✅ Camino implementado (
Compile→BuildReady/BuildFailed) y la maquinaria alrededor cubierta porcrates/hammerd/tests/bus_e2e.rs: handshake con peer creds, gatingno_cap,Query,Modifiedfan-out,Init→FIFO. UnCompilereal reusa el camino de Fase 0/1 (gated enHAMMER_NETWORK_TESTS).
Fase 6 — Integración de la IA ✅
- Crate
hammer-agentcon tres piezas: -AgentClient: cliente síncrono del bus (handshake,compile/inject/query/initbloqueantes, drenado de eventos asíncronos). -IntentTranslator(trait) +MockTranslatorcargado desde unIntentCatalogYAML (intent →.swmpre-armado). La integración con un LLM real se enchufa detrás del mismo trait sin tocar el bucle. -Orchestratorconrun(intent) → Proposal: plan → schema → base → try (overlay o prefix) → apply (mutaciones puras + source_patch vía bus opcional) → verify → propose. - CLI:
hammer ai <intent> --catalog F [--prefix DIR --base-ref F --bus SOCK]. - Tipos del protocolo del bus movidos a
hammer-core::protopara quehammerdyhammer-agentlos compartan. - Tests:
- 10 unit en
hammer-agent(translator + catalog + orchestrator). - 3 e2e del bucle agéntico (prefix tmp → archivos esperados en disco). - 1 e2e del cliente contra un stub del bus (handshake + Compile→BuildReady + Modified asíncrono). - Traductor LLM real (Claude API u otro), opcional vía feature flag o crate aparte.
ClaudeTranslatordetrás de la featurellm-claude, conureq + rustls. Modelo por defectoclaude-opus-4-8, adaptive thinking +effort=high. TraitHttpClientinyectable para tests sin red. CLI:hammer ai --llm(mutuamente excluyente con--catalog). Sin la feature, el flag falla con mensaje claro. - Lenguaje de consulta del sistema (SDD 08 §6) para que la IA refiera servicios y
archivos sin rutas frágiles. Forma
kind:value(bin,file,pin,service,depends), evaluable local (hammer query <expr>) y remoto vía bus (Command::Query{what:"expr"}). Parser ELF64 mínimo para extraerDT_NEEDED. - Bucle de auto-reparación (cliente reacciona a
Crashedcon un nuevo plan).Orchestrator::run_with_repair(intent, &RepairPolicy)drena async events del bus tras cada apply; si llegaCrashed, formatea una nueva intención y reentra hastamax_attempts. ElProposalfinal lleva elrepair_chaincompleto para que el humano vea la evolución antes de hacer commit. CLI:hammer ai --repair-max-attempts. - Hecho cuando: una intención en lenguaje natural produce un cambio probado en overlay,
presentado para
commithumano. ✅ Demostrado porhammer aiconMockTranslator: intent →.swm→ mutaciones aplicadas →Proposalconoverlay_id+ checks. El humano decidecommit/discard. El upgrade a LLM real reusa todo el bucle.
Track posterior — distro propia
Diseño completo en SDD 11 — Bootstrap from-scratch. Resumen:
- Bootstrap from-scratch en tres etapas (SDD 11 §3):
- Stage 0 ✅ — crate
hammer-bootstrap+hammer bootstrap stage0: ingiere el toolchain semilla pinned (SeedSpec, hash por identidad), verifica sha256 antes de sellar, idempotente. Cubierto por 4 unit + 1 e2e de CLI (offline,file://). - Stage 1 ✅ — userland mínimo, booteado end-to-end en QEMU. Recetas pinned
musl1.2.5 +busybox1.36.1 (estáticas, zig cc) +hammerd+arje-zero(recetas Cargo: repo pinned + deps vendoreadas en el fetch, build nativo crt-static víacargo rustc);hammer bootstrap stage1los construye con la semilla, ensambla el rootfs FHS y lo sella. El init es arje-zero como PID 1 (/sbin/init→arje-zero) con su seed card/ente/seed.card.json; al bootear, arje-zero carga+valida la seed y supervisa hammerd + getty (elagent.socky el watcher fanotify de hammerd levantan). ElCRASHEDreal está demostrado:kill hammerd→ arje lo detecta (Killed(SIGTERM)), programa restart con backoff y lo re-encarna — la supervisión del init propio que la Fase 5 difirió (ADR 0007). Diseño en SDD 12; corrida y fixes en el runbook. Pendiente: bus único (B.2: exponer elCRASHEDa la capa de IA) y atestación (A1/A2). - Stage 2 ✅ — rebuild nativo dentro del rootfs y diff de hashes ⇒ auto-alojamiento
bit-reproducible CONFIRMADO end-to-end (host↔VM).
KVM=1 MEM=24576 ./scripts/selfhost-verify.shreconstruye los 4/4 dentro del rootfs Stage 1 con sólo su propio toolchain (arje-zero cu=1 compila en ~27 min in-VM), sella el rootfs y suof_tree(stage1') = b3:0039b2b9…iguala la referencia ⇒✓ REPRODUCIBLE: stage1' == stage1. El último no-determinismo (#3,arje-zero/codegen-units) se cazó y eliminó imponiendoCARGO_PROFILE_RELEASE_CODEGEN_UNITS=1en el sandbox (commit4c9bcc0). Corrida y veredicto en el runbook §8.
- Stage 0 ✅ — crate
- Reemplazar el init de Alpine por tu init (bus por pipes nativo) — habilita el
CRASHEDreal que la Fase 5 dejó diferido. Decisión: adoptararje(init from-scratch ya existente en el monorepotawasuyu, con PID 1 + supervisión real) en lugar de escribir uno nuevo — ver ADR 0007. Trae bus único, CAS BLAKE3 compartido y confianza en capas (procedencia hammer + atestación arje). - Userland propio (busybox/toybox a elección),
/etc/service/*propios. - Decidir particionado/montaje de
/storey/var/lib/hammer. - Log de transparencia para compartir en comunidad (SDD 09 §4): el
bootstrap.jsonde SDD 11 §4 es su embrión. - Mini-lenguaje de consulta del sistema para la IA (SDD 08 §6).
Estado actual
- ✅ Fases 0–6 cerradas sobre Alpine: build determinista → hidratación → overlay
(con anidación LIFO) → diario (con
opCreate/Edit/Delete) →.swmfirmado → bus de agente → bucle agéntico con IA (mock + Claude real trasllm-claude). - ✅ El bucle completo está validado: una intención en lenguaje natural produce un cambio
probado en overlay, presentado para
commithumano. - ✅ 214 tests verdes en el workspace (1 e2e de overlayfs gated en
HAMMER_OVERLAY_TESTS; los de red enHAMMER_NETWORK_TESTS). - ⏳ Único ítem de las fases diferido:
CRASHEDreal (necesita supervisión de servicios → init propio del track posterior). - ✅ Track posterior arrancado y con su hito mayor cerrado: bootstrap from-scratch
Stage 0 → Stage 1 (booteado en QEMU, arje-zero como PID 1,
CRASHEDreal) → Stage 2 auto-alojamiento bit-a-bit confirmado (✓ REPRODUCIBLE, host↔VM). Baseline reproducible 4/4:of_tree(stage1)=b3:0039b2b9…(cu=1). - ✅ CERRADO — auto-alojamiento puro (variante b, SDD 11 §7.2b): que
hammer construya el toolchain del builder desde fuente (no Alpine), reemplazando piezas una a una
con Stage 2 verificando cada paso. Pieza 1 hecha: GNU make
4.4.1(recipes/make.toml) se compila desde fuente con el lab — estática musl, selladab3:fbad44ac…, reproducible bit-a-bit. Swap implementado:BuilderSpec.swaps+ el flaghammer bootstrap builder --swap make=<hash>montan elmakehammer sobre/toolchain/usr/bin/make(pisando el de Alpine) y lo anclan en el hash lógico del builder ⇒ la procedencia deja de ser "todo Alpine" y se vuelve auditable (purab3:8a370f5d…vs swappedb3:e7e2282c…en el host). El verify lo expone conSWAP_MAKE=1. Stage 2 in-VM con el swap (2026-06-13):✗ DIVERGENTE→ causa raíz hallada y arreglada, make inocente. El swap dioof_tree(stage1')=9adefb82… ≠ 0039b2b9…, pero el control sin swap dio el mismo9adefb82…⇒ el make no era la causa. Bisección (descartando uno a uno: musl/busybox idénticos con hammer-make incluso a-j1; hammerd reproduce): el único flaky era arje-zero, no-determinista incluso host-a-host (~62 KB de diferencia, mismo rustc/commit/flags) — el backend paralelo de rustc/LLVM (ThinLTO) quecodegen-units=1NO serializa. Builds seriales (la VM tiene 1 vCPU, otaskset -c 0) reproducían bit-a-bit a9adefb82…; los paralelos divergían. El viejo0039b2b9…era un build paralelo no-fiable. Fix:CARGO_BUILD_JOBS=1en el sandbox (serializa el jobserver ⇒ backend de 1 hilo, independiente del nº de CPUs) — verificado: arje-zero a todas las CPUs +JOBS=1da byte-idéntico al serial. Baseline del host refrescado yEXPECT_REFre-baseado a9adefb82…. Pieza 2 — busybox (sh+coreutils) validada en host: el/toolchainAlpine es mixto (busybox para sh/sed/grep/awk/tar/find, GNU coreutils para cp/mkdir/install); el swap monta el busybox estático de hammer sobre/toolchain/bin/busybox(--swap busybox=<hash>:bin/busybox, sin código nuevo). Con make+busybox de hammer los 4/4 (incl. busybox compilándose a sí mismo con hammer-busybox de shell) reproducenof_tree=9adefb82…. Expuesto conSWAP_BUSYBOX=1. ✓ REPRODUCIBLE in-VM con ambos swaps (2026-06-13):KVM=1 MEM=24576 SWAP_MAKE=1 SWAP_BUSYBOX=1 ./scripts/selfhost-verify.shenlibrereconstruyó los 4/4 dentro de la VM con el/toolchainhammerizado (makefbad44ac…+ busybox56664d70…pisando los de Alpine) y elof_tree(stage1')igualó la referencia9adefb82…⇒✓ REPRODUCIBLE: stage1' == stage1,DRIVER_RC=0(~54 min, arje-zero cu=1 in-VM). La procedencia del builder ya no es "todo Alpine" y el auto-alojamiento sigue bit-a-bit. Pieza 3 — linux-headers (recipes/linux-headers.toml, kernel 6.16.12, validada en host): los headers UAPI que musl/busybox#include. Construidos conmake headers(NOheaders_install: sursyncfinal falta en el toolchain hermético) +unifdefvía HOSTCC=zig cc. El swap es de directorio, no de binario: se extendióassemble_builderpara reemplazar árboles enteros (los 13 subdirs kernel-ownedusr/include/{linux,asm, asm-generic,cxl,fwctl,misc,mtd,rdma,regulator,scsi,sound,video,xen}), dejando intactos los de musl (bits/sys/net…). Hallazgo clave: el paquete de Alpine NO es elmake headerscrudo — trae 5 archivos distintos a la salida vainilla; 3 de ellos (scsi/{scsi,scsi_ioctl,sg}.h, legacy userspace, NO UAPI) son requeridos porque el appletejectde busybox los incluye y sin ellos no compila. Se aportan content-pinned víarecipes/linux-headers-alpine-compat.patch(+ 2 cosméticos: kernel.h/if_tunnel.h) y se pisan sobre la salida. Criterio de éxito fuerte alcanzado:diff -rdel header-tree hammer contra el de Alpine = vacío ⇒ el toolchain swapeado es byte-idéntico al baseline ⇒ todo lo que compile abajo reproduce de por sí (garantía por construcción, más fuerte que un rebuild). Expuesto conSWAP_LINUX_HEADERS=1; ensamblado end-to-end en host OK. Falta sólo la corrida in-VM para el sello. Pieza 4 — bwrap (recipes/bwrap.toml, bubblewrap 0.11.0, validada en host): EL sandbox del propio lab (hammer-build/src/sandbox.rs). Binario estático ⇒ swap de archivo sobre/toolchain/usr/bin/bwrap. Como tool del toolchain (no input del 4/4) NO necesita casar byte-a-byte con Alpine, sólo aislar igual. Tres sub-hallazgos que la hicieron más jugosa que make/busybox: (1) el toolchain Alpine no trae meson/ ninja/python — bypaseamos meson compilando los 4.cde bubblewrap directo conzig cc(+config.htrivial generado a mano); (2) depende de libcap, cuyo -dev (libcap.a +sys/capability.h) tampoco está en el toolchain ⇒ nueva recetarecipes/libcap.toml; (3) para que el build de bwrap viera libcap se agregó materialización de build-deps al lab:deps.buildahora se construye recursivamente y cada artefacto sellado se apila como capa--overlay-srcBAJO el rootfs del sandbox, dejando sususr/{include,lib,lib/pkgconfig}en/usr— pkgconf y zig cc los hallan sin plumbing de flags (recetas sin deps quedan byte-iguales, baseline intacto). Validación host fuerte: hammer-bwrap es estático, corre--version/sandboxea, y musl rebuildeó byte-idéntico usándolo de sandbox (bisección). Expuesto conSWAP_BWRAP=1; falta sólo correrla in-VM. Pieza 5 — coreutils (recipes/coreutils.toml, GNU 9.8, validada en host): cp/mkdir/ln/chmod/mv/ install que elmake installde musl/busybox usa. Empaquetada multicall (--enable-single-binary, igual layout que Alpine: un/bin/coreutils+ ~100 symlinks), así un solo swap del binario rutea todos los applets. Tool, no input ⇒ build vainilla sin patches. Bisección host fuerte: musl Y busybox rebuildearon byte-idéntico (57b66a2e,56664d70) con hammer-coreutils pisando el de Alpine. Expuesto conSWAP_COREUTILS=1. Falta correrla in-VM. Herramientas de toolchain extra desde fuente (provenance, no en el camino del 4/4 mínimo):recipes/{patch,m4,pkgconf}.toml— GNU patch 2.8 (lo usaapply_patches), GNU m4 1.4.20 (base autotools) y pkgconf 2.5.1 (lee los.pcque materializa el lab). Construidas estáticas con zig cc y validadas (corren, versión correcta, funcionales). El 4/4 mínimo (musl/busybox/hammerd/arje-zero) no las invoca, así que no llevan flagSWAP_*dedicado: son swapeables con el escapeSWAPS="name=hash:rel". Avanzan el "builder reconstruible al completo, no todo-Alpine" aunque el verify no las ejercite hoy. ✅ CORRIDA in-VM acumulada (2026-06-14): los 5 swaps de sandbox juntos✓ REPRODUCIBLE.KVM=1 MEM=... SWAP_MAKE=1 SWAP_BUSYBOX=1 SWAP_LINUX_HEADERS=1 SWAP_BWRAP=1 SWAP_COREUTILS=1 ./scripts/selfhost-verify.shreconstruyó los 4/4 dentro de la VM con el/toolchainhammerizado y elof_tree(stage1')igualó9adefb82…bit a bit (~46 min, sobrevive presión de memoria). Recetas extra de toolchain desde fuente añadidas después:recipes/binutils.toml(GNU binutils 2.45.1, CC=gcc porque zig miscompila → segfault, link dinámico, inerte al of_tree porque zig ya provee as/ld/ar; swapeable víaSWAPS=). - ✅✅✅ CERRADO — frente rust/llvm (la última pieza grande del toolchain): cadena purista
mrustc → rustc 1.90.0 → 1.91.0 → 1.91.1conx.pyreal, reusando un LLVM 20.1.8 externo en todo (sin rebuild).rust-1.91.1-prefix/bin/{rustc,cargo}corren standalone (rpath, host musl), versión EXACTA de Alpine. Auto-consistencia VERIFICADA in-VM (2026-06-18):SWAP_RUST=1 KVM=1 PRESEED=hammerd ./scripts/selfhost-verify.sh→✓ REPRODUCIBLEbit a bit (host y VM danof_tree=7fa6cb4e…=RUST_EXPECT_REF, ≠9adefb82…Alpine). Para el rebuild bit-reproducible se committeó elCargo.lockde arje-zero en tawasuyu + se bumpeó la receta (--locked);PRESEED=hammerdes OBLIGATORIO (arje-zero vendorea ~1973 crates y un rebuild in-VM completo desborda RAM). Artefactos enscripts/rust-frontier/. - ✅✅✅ CAPSTONE (2026-06-19): LOS 6 SWAPS JUNTOS
✓ REPRODUCIBLEIN-VM — los 5 tools de sandbox +SWAP_RUSTa la vez,of_tree=7fa6cb4e…bit a bit (~7 min,MEM=12288 PRESEED=hammerd, sin swap en el host). El auto-alojamiento del toolchain del builder está COMPLETO; las piezas inertes al 4/4 (binutils/pkgconf) quedan salteadas por diseño. Capa de arranque restante (status libc, no vale de-Alpinizar): gcc, m4, libz/libzstd runtime. - ✅✅✅ CERRADO — kernel-from-source (SDD 11, último eslabón de soberanía): el
kernel Linux 6.16.12 construido por hammer (
recipes/linux.toml,CC=gcc, monolítico e1000/overlay/userns=y) BOOTEA la VM del selfhost-verify y el rebuild in-VM da✓ REPRODUCIBLEbit a bit (HAMMER_KERNEL=1 KERNEL=<bzImage> KVM=1 MEM=16384 PRESEED=hammerd). Config lean (build ~35→~14 min, bzImage 13→8.7 MB). El muropivot_root(/= rootfs absoluto del mount-ns ⇒ bwrap no pivota; quirk que el kernel host enmascaraba) se resolvió con un/initwrapperswitch_roota tmpfs (condicional aHAMMER_KERNEL=1, no toca el camino del kernel host). Build-deps del kernel TODOS de-Alpinizados:recipes/{flex,bison}.toml(kconfig),recipes/openssl.toml(3.5.4, certs/extract-cert),recipes/elfutils.toml(sólo libelf 0.194 para objtool, con shims musl mínimos). Capa de arranque restante: m4, libz/libzstd, gcc (status libc). - ✅ Etapa A — orquestador
bootstrap all+ log de transparencia exportable:hammer_bootstrap::allencadena stage0→stage1→stage2 en una corrida del host (hammer bootstrap all --url … --sha256 … --version …) y deja elbootstrap.jsonpoblado;hammer bootstrap manifestlo imprime (la superficie de publicación del log, SDD 11 §4). El veredicto✓ REPRODUCIBLElo sigue sellando el rebuild in-VM (selfhost-verify.sh), no el host —allancla la referencia para que ese rebuild la compare. Pendiente de la Etapa A: el smoke end-to-end de I3 (bus único) contra un init vivo (arje+hammerd corriendo) — no reproducible sin la VM, mismo status que cualquier sello in-VM. - ✅ B.2 —
CRASHEDreal a la capa de IA (cableado end-to-end): elEvent::Crasheddel bus de agente ya tiene fuente real. Fuente (arje):arje-busganóBusRequest::SubscribeBusPayload::Event(BusEvent); arje-zero difunde enon_deathEnteCrashed{id,label,status}/EnteRestarting{delay_ms}/EnteExiteda las conexiones suscritas, purgando las muertas (4 tests). Sink (hammer):hammerd::crashestraduce la señal normalizada →Event::Crashed→EventBus→/run/agent.sock(3 tests). Transporte (hammer):hammerd::arje_linkse suscribe al bus de arje ($ENTE_BUS_SOCK) y reenvía cadaBusEvent. Decisión de wire = relectura del frame postcard (mirror mínimo del subconjunto de suscriptor; no arrastra el crate-graph de arje ⇒ hammer sigue standalone). El layout está verificado byte-a-byte contraarje-busreal (mismas versiones ulid 1.2 / postcard 1.1; frame Subscribe =[00,01,00,0d]) y cubierto por un round-trip local (2 tests). Resta sólo el smoke end-to-end contra un init vivo (no reproducible sin arje corriendo).
- ⏭️ También pendiente (Stage 1): atestación arje (A1/A2 — ya cableada en el repo arje, resta sólo enchufar su veredicto de boot a este roadmap).
Notas de entorno
- Desarrollo principal: laptop del autor.
- Host actual: Proxmox (
gioser.net). La VM/LXC Alpine de pruebas vivirá aquí o en la laptop. - Repo:
https://gitea.gioser.net/sergio/hammer.