Sincroniza las cabeceras con la realidad: tras cerrar la op del watcher (Fase 3) y la anidación LIFO (Fase 2), ningún ítem de las fases 0–6 queda abierto salvo CRASHED real, explícitamente diferido al track posterior. Reescribe "Estado actual" (seguía describiendo el día 1) con el bucle agéntico validado, 214 tests verdes y el siguiente paso: bootstrap from-scratch del track posterior. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
14 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. 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 ✅ (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
- 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
- ✅ 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). - ⏭️ Siguiente: arrancar el track posterior (distro propia). Primer paso natural sin
bloquear nada: el bootstrap from-scratch Stage 0/1 con
zig/musl-cross-make.
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.