tawasuyu

sergio/takana

> Una distribución Linux construida con un laboratorio funcional y hermético en el > sótano, y una terminal mutable, clásica y anárquica en el piso de arriba — diseñada > desde el suelo para que una IA programadora entienda, modifique y co…

TOML 1182Shell 87Rust 63Markdown 541592 Definiciones

⎇ ec7ceb0915f28c50f35472087d36d958cafe09a1 docs/03-hydration.md Markdown · 112 líneas · 5.3 KB Historia crudo

feat(hydrate): hidratación dinámica real con patchelf (LinkMode::Dynamic) f6b337f6c · Sergio ·

1# SDD 03 — Hidratación y store
2
3La hidratación es el puente del sótano al piso de arriba: cómo un artefacto del store
4content-addressed termina siendo un `/bin/grep` normal, mutable, en un FHS clásico — sin
5arrastrar la ruta del store grabada a fuego, y sin el infierno de symlinks de Nix.
6
7## 1. El store
8
9```
10/store/<blake3>-<name>/        un artefacto sellado e inmutable
11    bin/grep
12    share/...
13```
14
15- **Inmutable y append-only.** Nada modifica un artefacto sellado. Reconstruir produce un
16  hash nuevo, una entrada nueva.
17- **Direccionado por contenido.** El prefijo es el `artifact_hash` (ver [SDD 02](02-build-lab.md)).
18- **Deduplicado.** Dos recetas que producen el mismo árbol comparten entrada.
19- **GC por alcanzabilidad.** Un artefacto es basura si ningún hardlink del FHS ni ningún pin
20  lo referencia. `hammer gc` libera lo inalcanzable.
21
22## 2. El problema que resolvemos
23
24Un binario compilado en un entorno funcional lleva grabado:
25
26- el **intérprete ELF** (el loader de la libc): p. ej. `/store/<hash>/lib/ld-musl-x86_64.so.1`,
27- el **RPATH** (dónde busca las `.so` dinámicas): rutas dentro del store.
28
29Si copias ese binario tal cual a `/bin`, falla: busca la libc en una ruta del store. Hay que
30**normalizar** las rutas al esquema clásico antes de que toque el FHS real.
31
32## 3. Estrategia primaria: enlazado estático (la vía limpia)
33
34Con musl, el camino de oro es **enlazar todo estáticamente** (`link = "static"`). El resultado
35es un único binario autosuficiente, sin `.so`, sin intérprete externo, sin RPATH que importe.
36
37- **Hidratar = hardlink directo** del binario del store a `/bin`/`/sbin`.
38- **Estructura familiar:** `/bin` real lleno de ejecutables normales; cero symlinks crípticos.
39- **Control total:** ¿reemplazar `ls` por una versión experimental compilada a mano? Copias el
40  binario encima y listo (ver §6, copy-on-write).
41
42Esta es la estrategia por defecto.
43
44## 4. Estrategia secundaria: purga de RPATH con `patchelf` (la vía dinámica)
45
46Cuando un paquete requiere enlazado dinámico (plugins, tooling específico), el lab compila
47contra el store, y la hidratación **reescribe el binario** antes de inyectarlo:
48
491. `patchelf --set-interpreter /lib/ld-musl-x86_64.so.1 <bin>` — loader estándar del sistema.
502. `patchelf --set-rpath /lib:/usr/lib <bin>` — rutas clásicas.
513. Hardlink/copia del binario normalizado y de sus `.so` a `/lib`.
52
53`patchelf` (irónicamente, creado por el equipo de Nix) es la herramienta exacta para esto.
54
55**Implementado** (`hammer-build::hydrate`, `LinkMode::Dynamic` + `DynamicSpec{interpreter,rpath}`):
56el walker detecta ELF por magic (`\x7fELF`) y, si el `DynamicSpec` trae interpreter y/o
57rpath, **copia** ese binario (no lo hardlinkea: patchelf lo reescribiría y mutaría el inode del
58store) a un `.hammer-tmp`, lo parchea con `patchelf --set-interpreter/--set-rpath` y lo renombra
59atómicamente. Los no-ELF (y los ELF cuando el spec está vacío) siguen hardlinkeándose como en
60estático. Si falta `patchelf` en el PATH se falla limpio **antes** de tocar el FHS
61(`ensure_patchelf`). `HydrateReport.patched` cuenta los binarios reescritos.
62
63## 5. El core dinámico curado (matiz honesto sobre el ABI de musl)
64
65La idea de "actualizar el sustrato atómicamente y que los binarios no se enteren" **sólo es
66segura si se versiona explícitamente.** musl **no garantiza estabilidad de ABI entre
67versiones**; confiar ciegamente en ello rompería binarios.
68
69Política de `hammer`:
70
71- El grueso del sistema se enlaza **estático** → inmune al problema.
72- Un subconjunto hiper-reducido de librerías core de actualización frecuente por seguridad
73  (p. ej. `openssl`, `zlib`) puede vivir **dinámico** en una ruta controlada `/lib/core/`,
74  **versionada** (`/lib/core/libssl.so.3`).
75- Las actualizaciones de ese core respetan el `soname`/versión. Si una actualización cambia el
76  ABI, **es un artefacto nuevo con soname nuevo**, no un reemplazo silencioso.
77- Para plugins reales, preferir `dlopen` versionado sobre fe en el ABI.
78
79Resultado: el superpoder de "reemplazar el suelo" existe, pero acotado y honesto, sin la
80promesa mágica que rompería en producción.
81
82## 6. Mutabilidad: copy-on-write sobre hardlinks
83
84El FHS está hidratado por **hardlinks** al store (no symlinks → rutas reales; no copias →
85dedup). El store es inmutable; el FHS es mutable. ¿Cómo conviven?
86
87- **Leer/ejecutar:** el hardlink apunta al inode del store. Cero copia.
88- **Escribir/pisar en caliente:** al reemplazar `/bin/grep`, **se rompe el hardlink** (se
89  escribe un inode nuevo). El artefacto del store queda intacto → base de rollback.
90- **Rollback:** re-hidratar desde el store restaura el hardlink original.
91
92Esto da exactamente lo buscado: "un `rm -rf` rompe cosas de verdad", pero siempre hay una base
93limpia reconstruible debajo.
94
95## 7. Interfaz
96
97```rust
98// hammer-core
99pub struct Store { root: PathBuf }
100impl Store {
101    pub fn has(&self, h: &ArtifactHash) -> bool;
102    pub fn seal(&self, out_dir: &Path, h: ArtifactHash) -> Result<PathBuf>;
103    pub fn path_of(&self, h: &ArtifactHash) -> PathBuf;
104    pub fn gc(&self, roots: &[ArtifactHash]) -> Result<GcReport>;
105}
106
107// hammer-build
108pub fn hydrate(h: &ArtifactHash, store: &Store, target_fhs: &Path, mode: LinkMode)
109    -> Result<Vec<HydratedFile>>;
110```
111
112CLI: `hammer hydrate <hash> [--into /]` — proyecta el artefacto al FHS (real o de overlay).

03-hydration.md

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/...

2. El problema que resolvemos

Un binario compilado en un entorno funcional lleva grabado:

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.

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:

  1. patchelf --set-interpreter /lib/ld-musl-x86_64.so.1 <bin> — loader estándar del sistema.
  2. patchelf --set-rpath /lib:/usr/lib <bin> — rutas clásicas.
  3. Hardlink/copia del binario normalizado y de sus .so a /lib.

patchelf (irónicamente, creado por el equipo de Nix) es la herramienta exacta para esto.

Implementado (hammer-build::hydrate, LinkMode::Dynamic + DynamicSpec{interpreter,rpath}): el walker detecta ELF por magic (\x7fELF) y, si el DynamicSpec trae interpreter y/o rpath, copia ese binario (no lo hardlinkea: patchelf lo reescribiría y mutaría el inode del store) a un .hammer-tmp, lo parchea con patchelf --set-interpreter/--set-rpath y lo renombra atómicamente. Los no-ELF (y los ELF cuando el spec está vacío) siguen hardlinkeándose como en estático. Si falta patchelf en el PATH se falla limpio antes de tocar el FHS (ensure_patchelf). HydrateReport.patched cuenta los binarios reescritos.

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:

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?

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).