Cierra el último hueco de "sólo inline": ahora un .swm puede referenciar el patch y el contenido de un file_drop por URL. - hammer-build/src/download.rs: fetch_url_bytes / fetch_url_to_file (curl, sólo fuera del sandbox; acepta file:// para tests offline). 3 tests. - swm_bridge::build_source_patch: descarga patch_url a swm-recipes/<commit>.patch antes de compilar (ya no lo rechaza). Test reescrito con file://. - CLI apply: file_drop con content_url descarga y verifica BLAKE3 ANTES de escribir; hash erróneo ⇒ nada tocado (verificado e2e). Integridad: content_hash (file_drop) y build reproducible + expected_hash (patch). 22 binarios de test verdes; e2e por file:// (apply + caso de hash inválido que no escribe). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
11 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 solo overlay activo por target hoy).
Fase 3 — Diario de mutaciones ▶ en progreso
- 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 op del watcher (Create vs Replace vs Edit con FAN_REPORT_DFID_NAME).
- Hash del contenido tras la mutación (
content_hash) y de-dup idempotente.
Fase 4 — Formato y flujo .swm ▶ en progreso
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. Sourcegitmodelado;tarballcae a file_drop con warning (pendiente extender SourcePatch). - 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 ▶ en progreso
/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 ▶ en progreso
- 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
- Reemplazar el init de Alpine por tu init (bus por pipes nativo).
- Bootstrap from-scratch con
zig/musl-cross-make(Stage 0/1/2 → imagen destino). - 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).
- Mini-lenguaje de consulta del sistema para la IA (SDD 08 §6).
Estado actual
- ✅ Repo y workspace Rust inicializados.
- ✅ SDDs y ADRs redactados.
- ✅ Esqueletos de crates que compilan (
hammer buildstub). - ⏭️ Siguiente: implementar el sandbox de build real (Fase 0) y montar la VM/LXC Alpine.
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.