Files
hammer/docs/10-roadmap.md
T
Sergio 5986200539 Fase 6 — traductor LLM real (Claude API) detrás de feature llm-claude
`ClaudeTranslator` implementa el trait `IntentTranslator` igual que el
`MockTranslator`, así que el `Orchestrator` no cambia: se traduce una
intención NL a un `.swm` válido vía `/v1/messages`.

Diseño:
- Síncrono (ureq + rustls), consistente con `AgentClient`. Tokio
  entraría sólo si el resto del crate lo pidiera.
- Trait `HttpClient` inyectable ⇒ tests sin red contra una fake que
  captura el request y devuelve un body pre-armado.
- Modelo por defecto: `claude-opus-4-8` (Opus 4.8, el más capaz al día
  de hoy). Adaptive thinking + `effort=high` por defecto. Override por
  env (`HAMMER_LLM_MODEL`, `HAMMER_LLM_EFFORT`, `HAMMER_LLM_BASE_URL`).
- System prompt documenta el shape exacto del `.swm` (4 variantes de
  mutación) y obliga JSON puro. Parseamos con `serde_json::from_str::
  <Swm>` + `verify_schema()` como gate adicional. Manejamos `refusal`,
  error envelopes de la API y code-fence markdown.

Activación:
- Sin feature: el módulo declara los tipos pero `ClaudeTranslator::new`
  está bajo cfg. El binario compila sin red.
- Con `--features llm-claude`: trae `ureq` con rustls, y la CLI activa
  `hammer ai --llm`.

CLI:
- `hammer ai` gana `--llm` (mutuamente excluyente con `--catalog`).
  Refactor de `run_ai` en helpers `run_with_mock_translator` /
  `run_with_llm_translator` para mantener legible el dispatch.
- Sin la feature, `--llm` falla con un mensaje claro pidiendo
  recompilar.

Tests (7 unit): happy path con verificación de URL/headers/body,
unwrap de markdown, `refusal`, error envelope, SWM mal formado,
verify_schema, helpers de strip_code_fence.
2026-06-10 16:29:27 +00:00

161 lines
9.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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](adr/0002-alpine-first.md).
---
## Fases (sobre Alpine)
### Fase 0 — Laboratorio de build ✅
- [x] `hammer-core`: tipos `Recipe`, `ArtifactHash`, `Store` (BLAKE3, layout `/store`).
- [x] `hammer-build`: sandbox con `bubblewrap` + `zig cc` → binario musl estático.
- [x] CAS: hashing de entrada, caché por hash, sellado en el store.
- [x] `hammer build <recipe.toml>` → imprime el hash del artefacto.
- [x] Fuente git **y** tarball (sha256 fijado), patches opcionales.
- [x] Heurística de build system: autotools / cmake / meson / make plano.
- [x] Caché persistente de zig entre builds (~30s/build ahorrados).
### Fase 1 — Hidratación ✅
- [x] `hydrate(hash, target, mode)`: hardlink a FHS, `patchelf` para el caso dinámico.
- [x] `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 ✅
- [x] Crate `hammer-overlay`: `try` / `commit` / `discard` / `status` sobre `overlayfs`.
- [x] Manifiesto persistente en `<state_root>/<id>/state.json`; `status` enumera.
- [x] `commit` promociona archivos y procesa whiteouts (char dev 0/0) como remove.
- [x] Subcomandos del CLI: `hammer try [targets…]`, `commit <id>`, `discard <id>`, `status`.
- [x] Tests E2E con bwrap+user-ns, gateados en `HAMMER_OVERLAY_TESTS=1` (kernel-dependiente).
- [x] Hook al diario en `commit` (Fase 3 lo añadió vía `commit_with_journal`).
- [ ] Anidación real de overlays (un solo overlay activo por target hoy).
### Fase 3 — Diario de mutaciones ▶ *en progreso*
- [x] Crate `hammer-journal`: `MutationEvent`, `Source` (HammerHydrate/HammerCommit/External),
append/read/tail/follow JSON-líneas. RFC 3339 sin chrono.
- [x] `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.
- [x] `hammer journal [--tail N] [--follow] [--format pretty|json]`.
- [x] `hammer commit` registra cada archivo promocionado vía `commit_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*
- [x] `Swm` de/serialización YAML estable (roundtrip).
- [x] `verify_schema` (estructura + invariantes por mutación) y `verify_base` (distro_version
+ pins) con `BaseRef` / `BaseCompat`.
- [x] `apply_config_edit` (hunks `-/+` con búsqueda exacta, no-fuzz) y `apply_file_drop`
(base64 inline + verificación BLAKE3 vs `content_hash`).
- [x] Bridge `Mutation::SourcePatch``Recipe` + `build` + hidratación.
- [x] CLI: `hammer apply [--prefix DIR] [--base-ref base.json]`,
`hammer swm-verify`, `hammer export --journal DIR > out.swm`.
- [ ] `patch_url` / `content_url` remotos (hoy sólo inline).
- [ ] Provenance en `export`: mapa artefacto→receta para emitir `source_patch` en vez de
`file_drop`. Hoy se emiten file_drops con `content_b64`, lo que reproduce byte-a-byte
pero pierde la receta original.
- [ ] Firma `signature` (ed25519) y `TrustStore` local.
- **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 ▶ *en progreso*
- [x] `/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.
- [x] `/run/agent.sock` (JSON-líneas) con handshake `Hello/Welcome`, auth via
`SO_PEERCRED` y `CapsPolicy` por UID.
- [x] Comandos `Compile`/`Inject`/`Query`/`Init`; eventos
`Welcome`/`BuildReady`/`BuildFailed`/`Injected`/`QueryResult`/`InitAck`/
`Modified`/`Crashed`/`Error`.
- [x] EventBus in-process: el watcher publica `Modified` y todas las conexiones lo reciben.
- [x] Subsistemas independientes en `hammerd::main` (FIFO/watcher/bus en threads); si uno
falla en init, los demás siguen.
- [ ] Política expresiva: hoy es código (`default_policy`), pendiente leerla de
`/etc/hammer/agent-caps.toml`.
- [ ] `CRASHED` real (requiere supervisión de servicios, que llega con el init propio del
track posterior).
- [ ] `BuildFailed.log_tail` con cola real del lab (hoy es `None`; el `reason` viene del
`Error` de Rust pero no se conserva el log textual del sandbox).
- **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 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 ▶ *en progreso*
- [x] 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.
- [x] CLI: `hammer ai <intent> --catalog F [--prefix DIR --base-ref F --bus SOCK]`.
- [x] Tipos del protocolo del bus movidos a `hammer-core::proto` para que `hammerd` y
`hammer-agent` los compartan.
- [x] 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).
- [x] 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.
- [x] 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`.
- [x] 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](09-trust-model.md) §4).
- Mini-lenguaje de consulta del sistema para la IA ([SDD 08](08-ai-integration.md) §6).
---
## Estado actual
- ✅ Repo y workspace Rust inicializados.
- ✅ SDDs y ADRs redactados.
- ✅ Esqueletos de crates que compilan (`hammer build` stub).
- ⏭️ 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`.