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.6 KiB
SDD 03 — Hidratación y store
La hidratación es el puente del sótano al piso de arriba: cómo un artefacto del store
content-addressed termina siendo un /bin/grep normal, mutable, en un FHS clásico — sin
arrastrar la ruta del store grabada a fuego, y sin el infierno de symlinks de Nix.
1. El store
/store/<blake3>-<name>/ un artefacto sellado e inmutable
bin/grep
share/...
- Inmutable y append-only. Nada modifica un artefacto sellado. Reconstruir produce un hash nuevo, una entrada nueva.
- Direccionado por contenido. El prefijo es el
artifact_hash(ver SDD 02). - Deduplicado. Dos recetas que producen el mismo árbol comparten entrada.
- GC por alcanzabilidad. Un artefacto es basura si ningún hardlink del FHS ni ningún pin
lo referencia.
hammer gclibera lo inalcanzable.
2. El problema que resolvemos
Un binario compilado en un entorno funcional lleva grabado:
- el intérprete ELF (el loader de la libc): p. ej.
/store/<hash>/lib/ld-musl-x86_64.so.1, - el RPATH (dónde busca las
.sodinámicas): rutas dentro del store.
Si copias ese binario tal cual a /bin, falla: busca la libc en una ruta del store. Hay que
normalizar las rutas al esquema clásico antes de que toque el FHS real.
3. Estrategia primaria: enlazado estático (la vía limpia)
Con musl, el camino de oro es enlazar todo estáticamente (link = "static"). El resultado
es un único binario autosuficiente, sin .so, sin intérprete externo, sin RPATH que importe.
- Hidratar = hardlink directo del binario del store a
/bin//sbin. - Estructura familiar:
/binreal lleno de ejecutables normales; cero symlinks crípticos. - Control total: ¿reemplazar
lspor una versión experimental compilada a mano? Copias el binario encima y listo (ver §6, copy-on-write).
Esta es la estrategia por defecto.
4. Estrategia secundaria: purga de RPATH con patchelf (la vía dinámica)
Cuando un paquete requiere enlazado dinámico (plugins, tooling específico), el lab compila contra el store, y la hidratación reescribe el binario antes de inyectarlo:
patchelf --set-interpreter /lib/ld-musl-x86_64.so.1 <bin>— loader estándar del sistema.patchelf --set-rpath /lib:/usr/lib <bin>— rutas clásicas.- Hardlink/copia del binario normalizado y de sus
.soa/lib.
patchelf (irónicamente, creado por el equipo de Nix) es la herramienta exacta para esto.
5. El core dinámico curado (matiz honesto sobre el ABI de musl)
La idea de "actualizar el sustrato atómicamente y que los binarios no se enteren" sólo es segura si se versiona explícitamente. musl no garantiza estabilidad de ABI entre versiones; confiar ciegamente en ello rompería binarios.
Política de hammer:
- El grueso del sistema se enlaza estático → inmune al problema.
- Un subconjunto hiper-reducido de librerías core de actualización frecuente por seguridad
(p. ej.
openssl,zlib) puede vivir dinámico en una ruta controlada/lib/core/, versionada (/lib/core/libssl.so.3). - Las actualizaciones de ese core respetan el
soname/versión. Si una actualización cambia el ABI, es un artefacto nuevo con soname nuevo, no un reemplazo silencioso. - Para plugins reales, preferir
dlopenversionado sobre fe en el ABI.
Resultado: el superpoder de "reemplazar el suelo" existe, pero acotado y honesto, sin la promesa mágica que rompería en producción.
6. Mutabilidad: copy-on-write sobre hardlinks
El FHS está hidratado por hardlinks al store (no symlinks → rutas reales; no copias → dedup). El store es inmutable; el FHS es mutable. ¿Cómo conviven?
- Leer/ejecutar: el hardlink apunta al inode del store. Cero copia.
- Escribir/pisar en caliente: al reemplazar
/bin/grep, se rompe el hardlink (se escribe un inode nuevo). El artefacto del store queda intacto → base de rollback. - Rollback: re-hidratar desde el store restaura el hardlink original.
Esto da exactamente lo buscado: "un rm -rf rompe cosas de verdad", pero siempre hay una base
limpia reconstruible debajo.
7. Interfaz
// hammer-core
pub struct Store { root: PathBuf }
impl Store {
pub fn has(&self, h: &ArtifactHash) -> bool;
pub fn seal(&self, out_dir: &Path, h: ArtifactHash) -> Result<PathBuf>;
pub fn path_of(&self, h: &ArtifactHash) -> PathBuf;
pub fn gc(&self, roots: &[ArtifactHash]) -> Result<GcReport>;
}
// hammer-build
pub fn hydrate(h: &ArtifactHash, store: &Store, target_fhs: &Path, mode: LinkMode)
-> Result<Vec<HydratedFile>>;
CLI: hammer hydrate <hash> [--into /] — proyecta el artefacto al FHS (real o de overlay).