Files
takana/docs/10-roadmap.md
T
Sergio 476168bb07 takana etapa 5c: comentarios de scripts, MOTD, y un BUG que introdujo la etapa 4
250 líneas de comentario en 151 scripts. Control verificado: el diff no toca
NI UNA línea que no empiece por #, y la sintaxis de los 151 pasa.

El barrido saltea heredocs y cadenas triples, y el guardián DISPARÓ 3 veces:
las tres eran el MOTD que el script escribe DENTRO de la imagen construida —
texto del producto, no comentario del script. Se cambiaron aparte y a
propósito, que es rebranding, no limpieza.

Y el hallazgo caro:  casaba contra ,
que es el TARGET de tracing — o sea el module_path!, o sea el nombre del crate.
La etapa 4 lo movió a  y el script quedó casando NADA. No fallaba:
imprimía cero atribuciones, indistinguible de un log sin problemas. Comprobado
con el binario (RUST_LOG=info sobre zlib), no deducido. Ahora acepta las dos, y
tiene que seguir aceptándolas porque los logs viejos en disco dicen la vieja.

Además 14 rutas de módulo  en docs, que el barrido anterior no tocó
porque  no es frontera de palabra.
2026-09-09 19:28:48 +00:00

28 KiB
Raw Blame History

SDD 10 — Roadmap

Estrategia: validar la capa AI-nativa sobre Alpine antes de la distro propia

La innovación de takana 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 06: montamos takana sobre 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

  • takana-core: tipos Recipe, ArtifactHash, Store (BLAKE3, layout /store).
  • takana-build: sandbox con bubblewrap + zig cc → binario musl estático.
  • CAS: hashing de entrada, caché por hash, sellado en el store.
  • takana 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, patchelf para el caso dinámico.
  • takana 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 takana-overlay: try / commit / discard / status sobre overlayfs.
  • Manifiesto persistente en <state_root>/<id>/state.json; status enumera.
  • commit promociona archivos y procesa whiteouts (char dev 0/0) como remove.
  • Subcomandos del CLI: takana try [targets…], commit <id>, discard <id>, status.
  • Tests E2E con bwrap+user-ns, gateados en TAKANA_OVERLAY_TESTS=1 (kernel-dependiente).
  • Hook al diario en commit (Fase 3 lo añadió vía commit_with_journal).
  • Anidación real de overlays. Un try sobre un target ya cubierto apila un overlay nuevo (el kernel usa el merged view inferior como lowerdir). Orden de apilamiento total y determinista vía stack_key (created_at + id, con seq por proceso en fresh_id para evitar colisiones en ráfaga). commit/discard exigen LIFO: blocking_overlays detecta capas más jóvenes que solapan targets (igualdad o ancestro de path) y la operación falla con Error::Shadowed si 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) por overlay_nested_stack_inside_userns, gated en TAKANA_OVERLAY_TESTS. Pendiente menor: unicidad de orden entre procesos concurrentes (track posterior).

Fase 3 — Diario de mutaciones

  • Crate takana-journal: MutationEvent, Source (HammerHydrate/HammerCommit/External), append/read/tail/follow JSON-líneas. RFC 3339 sin chrono.
  • hammerd con fanotify (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.
  • takana journal [--tail N] [--follow] [--format pretty|json].
  • takana commit registra cada archivo promocionado vía commit_with_journal.
  • Refinar la op del watcher. watcher::classify_op(&journal, path, deleted) deriva Delete (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-Delete del path) o Create (primera mutación observada, o resurrección tras un Delete). Es la divergencia que el diario atestigua, no la verdad absoluta del FS. La atribución plena vía FAN_REPORT_DFID_NAME (nombre en eventos de directorio, rename-sobre-destino, open_by_handle_at+CAP_DAC_READ_SEARCH) queda para el track posterior: nix 0.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. takana_journal::content_hash_of/hash_file (blake3 plano, estilo b3sum, distinto del of_inputs de artefactos). Journal::record_dedup omite el evento si deja el archivo idéntico al último de ese path (o un Delete sobre algo ya borrado); last_for_path lo resuelve. El watcher hashea cada CLOSE_WRITE y usa record_dedup: una reescritura sin cambios ni ensucia el diario ni despierta el bus.

Fase 4 — Formato y flujo .swm

  • Swm de/serialización YAML estable (roundtrip).
  • verify_schema (estructura + invariantes por mutación) y verify_base (distro_version + pins) con BaseRef / BaseCompat.
  • apply_config_edit (hunks -/+ con búsqueda exacta, no-fuzz) y apply_file_drop (base64 inline + verificación BLAKE3 vs content_hash).
  • Bridge Mutation::SourcePatchRecipe + build + hidratación.
  • CLI: takana apply [--prefix DIR] [--base-ref base.json], takana swm-verify, takana export --journal DIR > out.swm.
  • patch_url / content_url remotos. takana_build::download (curl, sólo fuera del sandbox; acepta file:// para tests offline). build_source_patch descarga patch_url al swm-recipes/<commit>.patch antes de compilar; takana apply descarga content_url y verifica BLAKE3 antes de escribir (hash erróneo ⇒ nada tocado). Su integridad la cubre content_hash (file_drop) y el build reproducible + expected_hash (patch).
  • Provenance en export: mapa artefacto→receta vía sidecar .hammer/recipe.toml que takana-build::build escribe dentro del artefacto antes de sellar. takana export agrupa eventos por artifact_hash y emite UN source_patch por grupo cuya receta sea recuperable; lo demás cae al fallback file_drop. Source git y tarball modelados: Mutation::SourcePatch (y RecipeInline) llevan campos repo/commit xor tarball/sha256 (resueltos por swm::swm_source_kind, misma regla que recipe::Source::kind); takana export reconstruye ambos sin caer a file_drop.
  • Firma signature (ed25519) y TrustStore local. takana_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: takana keygen, takana swm-sign, y takana 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.rs para config_edit + file_drop. source_patch reusa el camino de Fase 0/1 (gated en TAKANA_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, y takana ctl <line> que escribe al FIFO con error claro si no existe.
  • /run/agent.sock (JSON-líneas) con handshake Hello/Welcome, auth via SO_PEERCRED y CapsPolicy por UID.
  • Comandos Compile/Inject/Query/Init; eventos Welcome/BuildReady/BuildFailed/Injected/QueryResult/InitAck/ Modified/Crashed/Error.
  • EventBus in-process: el watcher publica Modified y 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). takana_core::AgentCapsConfig: default + [[rule]] por uid/gid (primera que casa gana), caps_for(uid,gid). bus::policy_from_config la enchufa; sin fichero o con fichero inválido, cae a la política por-UID por defecto (avisando). Ejemplo en examples/agent-caps.toml. El default_policy hardcoded queda como fallback.
  • CRASHED real (requiere supervisión de servicios, que llega con el init propio del track posterior).
  • BuildFailed.log_tail con cola real del lab. Sandbox::run hace tee de stdout/stderr (sigue viéndose en vivo) y retiene las últimas 50 líneas; al fallar una fase devuelve BuildFailure { reason, log_tail } envuelto en Error::Other. El bus lo recupera por BuildFailure::from_error (downcast) y separa reason del 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 (CompileBuildReady/BuildFailed) y la maquinaria alrededor cubierta por crates/hammerd/tests/bus_e2e.rs: handshake con peer creds, gating no_cap, Query, Modified fan-out, Init→FIFO. Un Compile real reusa el camino de Fase 0/1 (gated en TAKANA_NETWORK_TESTS).

Fase 6 — Integración de la IA

  • Crate takana-agent con tres piezas: - AgentClient: cliente síncrono del bus (handshake, compile/inject/query/init bloqueantes, drenado de eventos asíncronos). - IntentTranslator (trait) + MockTranslator cargado desde un IntentCatalog YAML (intent → .swm pre-armado). La integración con un LLM real se enchufa detrás del mismo trait sin tocar el bucle. - Orchestrator con run(intent) → Proposal: plan → schema → base → try (overlay o prefix) → apply (mutaciones puras + source_patch vía bus opcional) → verify → propose.
  • CLI: takana ai <intent> --catalog F [--prefix DIR --base-ref F --bus SOCK].
  • Tipos del protocolo del bus movidos a takana-core::proto para que hammerd y takana-agent los compartan.
  • Tests: - 10 unit en takana-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. ClaudeTranslator detrás de la feature llm-claude, con ureq + rustls. Modelo por defecto claude-opus-4-8, adaptive thinking + effort=high. Trait HttpClient inyectable para tests sin red. CLI: takana 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 (takana query <expr>) y remoto vía bus (Command::Query{what:"expr"}). Parser ELF64 mínimo para extraer DT_NEEDED.
  • Bucle de auto-reparación (cliente reacciona a Crashed con un nuevo plan). Orchestrator::run_with_repair(intent, &RepairPolicy) drena async events del bus tras cada apply; si llega Crashed, formatea una nueva intención y reentra hasta max_attempts. El Proposal final lleva el repair_chain completo para que el humano vea la evolución antes de hacer commit. CLI: takana ai --repair-max-attempts.
  • Hecho cuando: una intención en lenguaje natural produce un cambio probado en overlay, presentado para commit humano. Demostrado por takana ai con MockTranslator: intent → .swm → mutaciones aplicadas → Proposal con overlay_id + checks. El humano decide commit/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 takana-bootstrap + takana 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 musl 1.2.5 + busybox 1.36.1 (estáticas, zig cc) + hammerd + arje-zero (recetas Cargo: repo pinned + deps vendoreadas en el fetch, build nativo crt-static vía cargo rustc); takana bootstrap stage1 los 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 (el agent.sock y el watcher fanotify de hammerd levantan). El CRASHED real 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 el CRASHED a 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.sh reconstruye 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 su of_tree(stage1') = b3:0039b2b9… iguala la referencia ⇒ ✓ REPRODUCIBLE: stage1' == stage1. El último no-determinismo (#3, arje-zero/codegen-units) se cazó y eliminó imponiendo CARGO_PROFILE_RELEASE_CODEGEN_UNITS=1 en el sandbox (commit 4c9bcc0). Corrida y veredicto en el runbook §8.
  • Reemplazar el init de Alpine por tu init (bus por pipes nativo) — habilita el CRASHED real que la Fase 5 dejó diferido. Decisión: adoptar arje (init from-scratch ya existente en el monorepo tawasuyu, 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 takana + atestación arje).
  • Userland propio (busybox/toybox a elección), /etc/service/* propios.
  • Decidir particionado/montaje de /store y /var/lib/hammer.
  • Log de transparencia para compartir en comunidad (SDD 09 §4): el bootstrap.json de SDD 11 §4 es su embrión.
  • Mini-lenguaje de consulta del sistema para la IA (SDD 08 §6).

Estado actual

  • Fases 06 cerradas sobre Alpine: build determinista → hidratación → overlay (con anidación LIFO) → diario (con op Create/Edit/Delete) → .swm firmado → bus de agente → bucle agéntico con IA (mock + Claude real tras llm-claude).
  • El bucle completo está validado: una intención en lenguaje natural produce un cambio probado en overlay, presentado para commit humano.
  • 214 tests verdes en el workspace (1 e2e de overlayfs gated en TAKANA_OVERLAY_TESTS; los de red en TAKANA_NETWORK_TESTS).
  • Único ítem de las fases diferido: CRASHED real (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, CRASHED real) → 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 takana 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, sellada b3:fbad44ac…, reproducible bit-a-bit. Swap implementado: BuilderSpec.swaps + el flag takana bootstrap builder --swap make=<hash> montan el make takana 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 (pura b3:8a370f5d… vs swapped b3:e7e2282c… en el host). El verify lo expone con SWAP_MAKE=1. Stage 2 in-VM con el swap (2026-06-13): ✗ DIVERGENTE → causa raíz hallada y arreglada, make inocente. El swap dio of_tree(stage1')=9adefb82… ≠ 0039b2b9…, pero el control sin swap dio el mismo 9adefb82… ⇒ el make no era la causa. Bisección (descartando uno a uno: musl/busybox idénticos con takana-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) que codegen-units=1 NO serializa. Builds seriales (la VM tiene 1 vCPU, o taskset -c 0) reproducían bit-a-bit a 9adefb82…; los paralelos divergían. El viejo 0039b2b9… era un build paralelo no-fiable. Fix: CARGO_BUILD_JOBS=1 en el sandbox (serializa el jobserver ⇒ backend de 1 hilo, independiente del nº de CPUs) — verificado: arje-zero a todas las CPUs + JOBS=1 da byte-idéntico al serial. Baseline del host refrescado y EXPECT_REF re-baseado a 9adefb82…. Pieza 2 — busybox (sh+coreutils) validada en host: el /toolchain Alpine es mixto (busybox para sh/sed/grep/awk/tar/find, GNU coreutils para cp/mkdir/install); el swap monta el busybox estático de takana sobre /toolchain/bin/busybox (--swap busybox=<hash>:bin/busybox, sin código nuevo). Con make+busybox de takana los 4/4 (incl. busybox compilándose a sí mismo con takana-busybox de shell) reproducen of_tree=9adefb82…. Expuesto con SWAP_BUSYBOX=1. ✓ REPRODUCIBLE in-VM con ambos swaps (2026-06-13): KVM=1 MEM=24576 SWAP_MAKE=1 SWAP_BUSYBOX=1 ./scripts/selfhost-verify.sh en libre reconstruyó los 4/4 dentro de la VM con el /toolchain hammerizado (make fbad44ac… + busybox 56664d70… pisando los de Alpine) y el of_tree(stage1') igualó la referencia 9adefb82…✓ 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 con make headers (NO headers_install: su rsync final falta en el toolchain hermético) + unifdef vía HOSTCC=zig cc. El swap es de directorio, no de binario: se extendió assemble_builder para reemplazar árboles enteros (los 13 subdirs kernel-owned usr/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 el make headers crudo — 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 applet eject de busybox los incluye y sin ellos no compila. Se aportan content-pinned vía recipes/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 -r del header-tree takana 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 con SWAP_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 (takana-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 .c de bubblewrap directo con zig cc (+config.h trivial generado a mano); (2) depende de libcap, cuyo -dev (libcap.a + sys/capability.h) tampoco está en el toolchain ⇒ nueva receta recipes/libcap.toml; (3) para que el build de bwrap viera libcap se agregó materialización de build-deps al lab: deps.build ahora se construye recursivamente y cada artefacto sellado se apila como capa --overlay-src BAJO el rootfs del sandbox, dejando sus usr/{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: takana-bwrap es estático, corre --version/sandboxea, y musl rebuildeó byte-idéntico usándolo de sandbox (bisección). Expuesto con SWAP_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 el make install de 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 takana-coreutils pisando el de Alpine. Expuesto con SWAP_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 usa apply_patches), GNU m4 1.4.20 (base autotools) y pkgconf 2.5.1 (lee los .pc que 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 flag SWAP_* dedicado: son swapeables con el escape SWAPS="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.sh reconstruyó los 4/4 dentro de la VM con el /toolchain hammerizado y el of_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ía SWAPS=).
  • CERRADO — frente rust/llvm (la última pieza grande del toolchain): cadena purista mrustc → rustc 1.90.0 → 1.91.0 → 1.91.1 con x.py real, 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✓ REPRODUCIBLE bit a bit (host y VM dan of_tree=7fa6cb4e… = RUST_EXPECT_REF, ≠ 9adefb82… Alpine). Para el rebuild bit-reproducible se committeó el Cargo.lock de arje-zero en tawasuyu + se bumpeó la receta (--locked); PRESEED=hammerd es OBLIGATORIO (arje-zero vendorea ~1973 crates y un rebuild in-VM completo desborda RAM). Artefactos en scripts/rust-frontier/.
  • CAPSTONE (2026-06-19): LOS 6 SWAPS JUNTOS ✓ REPRODUCIBLE IN-VM — los 5 tools de sandbox + SWAP_RUST a 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 takana (recipes/linux.toml, CC=gcc, monolítico e1000/overlay/userns=y) BOOTEA la VM del selfhost-verify y el rebuild in-VM da ✓ REPRODUCIBLE bit a bit (TAKANA_KERNEL=1 KERNEL=<bzImage> KVM=1 MEM=16384 PRESEED=hammerd). Config lean (build ~35→~14 min, bzImage 13→8.7 MB). El muro pivot_root (/ = rootfs absoluto del mount-ns ⇒ bwrap no pivota; quirk que el kernel host enmascaraba) se resolvió con un /init wrapper switch_root a tmpfs (condicional a TAKANA_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: takana_bootstrap::all encadena stage0→stage1→stage2 en una corrida del host (takana bootstrap all --url … --sha256 … --version …) y deja el bootstrap.json poblado; takana bootstrap manifest lo imprime (la superficie de publicación del log, SDD 11 §4). El veredicto ✓ REPRODUCIBLE lo sigue sellando el rebuild in-VM (selfhost-verify.sh), no el host — all ancla 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 — CRASHED real a la capa de IA (cableado end-to-end): el Event::Crashed del bus de agente ya tiene fuente real. Fuente (arje): arje-bus ganó BusRequest::Subscribe
    • BusPayload::Event(BusEvent); arje-zero difunde en on_death EnteCrashed{id,label,status} / EnteRestarting{delay_ms} / EnteExited a las conexiones suscritas, purgando las muertas (4 tests). Sink (takana): hammerd::crashes traduce la señal normalizada → Event::CrashedEventBus/run/agent.sock (3 tests). Transporte (takana): hammerd::arje_link se suscribe al bus de arje ($ENTE_BUS_SOCK) y reenvía cada BusEvent. Decisión de wire = relectura del frame postcard (mirror mínimo del subconjunto de suscriptor; no arrastra el crate-graph de arje ⇒ takana sigue standalone). El layout está verificado byte-a-byte contra arje-bus real (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.