Arranque del proyecto hammer (distro AI-nativa: laboratorio funcional en el sótano, terminal mutable clásica arriba, integración de IA programadora). - Workspace Rust (compila, tests verdes): hammer-core, hammer-build, hammer-cli (bin `hammer`), hammerd. - SDDs 00-10 + 6 ADRs en docs/ con toda la arquitectura. - Esqueletos navegables mapeados a las fases del roadmap; Fase 0/1 listas para implementar el sandbox real. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
4.1 KiB
SDD 05 — Diario de mutaciones
Este es el insight central de hammer: el diario es tu configuración del sistema — sin ser
declarativa. No declaras tu sistema en un archivo de texto antes de vivirlo; vives el sistema
imperativamente, y el propio sistema deriva, a posteriori, un mapa de tu desviación respecto
a la base limpia construida por el laboratorio.
Esto rompe la falsa dicotomía declarativo vs imperativo, y resuelve dos problemas a la vez:
- El de Arch/Gentoo: con los meses se pudren en archivos huérfanos porque el gestor pierde el rastro de lo que el usuario hizo a mano.
- El de Nix: para evitar lo anterior, te ata las manos (todo debe declararse antes).
1. El mecanismo
Un daemon ultraligero (hammerd) usa fanotify a nivel de kernel para registrar
exclusivamente las mutaciones sobre los directorios del sistema (/bin, /sbin, /lib,
/etc). No bloquea ninguna acción. Te deja destruir, mover y crear archivos a tu antojo. Pero
anota, en silencio, un diario de modificaciones.
fanotifysobreinotify: cobertura a nivel de superbloque/mount, eventos de modificación con info de PID/UID, y menor coste que vigilar recursivamente cada subdirectorio.
2. Qué registra (y qué no)
Registra mutaciones del FHS gestionado:
- creación / reemplazo / borrado de archivos en
/bin,/sbin,/lib,/etc, - qué los originó (PID, UID, y si fue una hidratación de
hammer, elartifact_hash), - ediciones de archivos de config en
/etc.
No registra:
- actividad efímera en
/tmp,/run,/proc,/sys,/dev, - lecturas/ejecuciones (sólo mutaciones),
- nada dentro de un overlay activo (el ruido del experimento no entra; sólo el
commit).
3. Formato del diario
Log append-only de texto plano (JSON-líneas), legible con cat/grep/jq. Cada línea es
un evento atómico. (Los timestamps los provee el daemon; el formato no impone reloj de
construcción.)
{"ts":"2026-06-06T18:40:01Z","op":"replace","path":"/bin/grep","by":{"uid":1000,"pid":4821},"artifact":"b3:9f2a…","note":"hydrate grep@perl-default"}
{"ts":"2026-06-06T18:41:12Z","op":"edit","path":"/etc/network.conf","by":{"uid":1000,"pid":4990},"diff_hash":"b3:7c1d…"}
{"ts":"2026-06-06T18:42:03Z","op":"manual-replace","path":"/bin/ls","by":{"uid":1000,"pid":5102},"artifact":null,"note":"usuario pisó ls a mano"}
artifact: null + op: manual-replace ⇒ el usuario reemplazó algo a mano fuera del flujo de
hammer. Eso no es un error; es información. El diario distingue "mutación trazable" (vino de
una receta/artefacto conocido) de "mutación opaca" (pisada manual), sin prohibir ninguna.
4. Exportar: del diario al .swm
hammer export lee el diario, lo recorta al delta relevante respecto a una base conocida,
y produce un manifiesto .swm (SDD 06):
- base = el conjunto de commits fijados + versión de distro de los que partiste.
- mutations = las mutaciones trazables convertidas a recetas (
source_patch) y las ediciones de config (config_edit), más reglas de init si aplican. - Las mutaciones opacas (pisadas manuales sin artefacto) se reportan como advertencia: no
se pueden compartir como receta reproducible.
hammerte dice exactamente cuáles y por qué.
Así, exportar el diario = exportar tu distro, pero sólo la parte que es reproducible y verificable. Lo que pisaste a ciegas, lo sabes y decides qué hacer con ello.
5. Replicar en otra máquina
No copias una config abstracta: exportas el diario de tus acciones humanas sobre el metal. La otra máquina, partiendo de la misma base, reproduce las recetas y aplica las ediciones. Tu sistema se replica desde su historia, no desde una declaración.
6. Interfaz
// hammerd / hammer-core
pub struct Journal { path: PathBuf }
impl Journal {
pub fn record(&self, ev: MutationEvent) -> Result<()>;
pub fn since(&self, base: &BaseRef) -> Result<Vec<MutationEvent>>;
pub fn export_swm(&self, base: &BaseRef) -> Result<(Swm, Vec<OpaqueWarning>)>;
}
CLI: hammer journal (ver/seguir el diario) · hammer export [--base <ref>] > my.swm.