Files
hammer/docs/10-roadmap.md
T
SergioandClaude Opus 4.8 9f7c1aa040 docs(roadmap): Fases 3/4/6 → , Fase 5 con CRASHED diferido, estado actual al día
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>
2026-06-10 20:41:56 +00:00

14 KiB
Raw Blame History

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 06: montamos hammer 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

  • hammer-core: tipos Recipe, ArtifactHash, Store (BLAKE3, layout /store).
  • hammer-build: sandbox con bubblewrap + 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, patchelf para 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 / 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: 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í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 HAMMER_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.
  • 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.
  • hammer journal [--tail N] [--follow] [--format pretty|json].
  • hammer 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. hammer_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: hammer apply [--prefix DIR] [--base-ref base.json], hammer swm-verify, hammer export --journal DIR > out.swm.
  • patch_url / content_url remotos. hammer_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; hammer 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 hammer-build::build escribe dentro del artefacto antes de sellar. hammer 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 modelado; tarball cae a file_drop con warning (pendiente extender SourcePatch).
  • Firma signature (ed25519) y TrustStore local. 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, y hammer 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 HAMMER_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 hammer 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). hammer_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 HAMMER_NETWORK_TESTS).

Fase 6 — Integración de la IA

  • Crate hammer-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: hammer ai <intent> --catalog F [--prefix DIR --base-ref F --bus SOCK].
  • Tipos del protocolo del bus movidos a hammer-core::proto para que hammerd y hammer-agent los 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. 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: 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 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: hammer ai --repair-max-attempts.
  • Hecho cuando: una intención en lenguaje natural produce un cambio probado en overlay, presentado para commit humano. Demostrado por hammer 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

  • 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 /store y /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 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 HAMMER_OVERLAY_TESTS; los de red en HAMMER_NETWORK_TESTS).
  • Único ítem de las fases diferido: CRASHED real (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.