La unidad 7 del SDD 26 (§6.2). Cuando una descarga TERMINA, la extensión `descargas@atuq.tawasuyu` le pasa al host la ruta, el nombre y de dónde salió; el host la ingiere a un CAS BLAKE3 y contesta el hash, el tamaño y **cuántas veces se bajó ese mismo contenido**. El mismo contenido con otro nombre es UN objeto y dos nombres, y el navegador lo dice — que es exactamente lo que ningún navegador sabe hacer: para todos una descarga es un blob con nombre, y por eso la bajás dos veces y no te enterás. No es un almacén nuevo: es `arje-cas`, el mismo formato y el mismo hash que usan arje, takana y tejido. El `<hex>` del CAS ES el `expected_hash` de un `.swm`. ⚠ TRES DECISIONES QUE SALIERON DE MEDIR, NO DE DISEÑAR: 1. **La raíz del CAS de descargas no es la del sistema.** `arje_cas::gc` borra todo blob que no esté en el set `reachable` de su llamador, y el único que existe (`arje-brain`, `GcCas`) lo arma con la cadena de audit y las raíces vivas del grafo. Una descarga del usuario no está en ninguno: el primer GC se la llevaría, en silencio. Van a `<estado>/descargas-cas` — mismo formato (mover un objeto al CAS del sistema es un `rename`), fuera del alcance del GC de otro. El precio queda escrito: tejido sirve el CAS por defecto, así que compartir una descarga hoy exige apuntarlo ahí. 2. **El fichero del usuario no se toca**: se copia y se deja donde estaba. El guardián lo comprueba byte a byte. Borrar o mover lo que alguien acaba de bajar no es decisión de un navegador. 3. **La extensión observa, no intercepta.** Se entera cuando la descarga terminó. Meterse en el medio obligaría a decidir qué pasa si el CAS falla, y la respuesta correcta —que la descarga siga igual— es lo que se consigue no metiéndose. Y EL HALLAZGO QUE COSTÓ LA TARDE, que no es de esta unidad: **en `--headless` el navegador se cae con SIGSEGV en cuanto una descarga termina.** Se atribuyó con dos controles antes de tocar nada: --headless, con la extensión → baja el fichero, TERMINADA, Segmentation fault (139) --headless, SIN la extensión → baja el fichero, Segmentation fault (139) ← no es nuestro --headless, panel de descargas apagado → Segmentation fault (139) sway headless (compositor REAL) → baja, INGIERE al CAS, y no se cae ← no es del producto Sin el primer control esto se leía como «la extensión de descargas rompe el navegador» (perseguir un bug que no existe); sin el último, como «atuq no puede descargar» (reportar un bug de producto que tampoco existe). Por eso `scripts/test-atuq-descargas.py` corre sobre sway y no sobre `--headless`, y por eso lo dice en su cabecera: un arnés distinto al de los demás guardianes, sin explicación, es una invitación a «simplificarlo» de vuelta al que se cae. Queda en el §6.10.bis del SDD, que es donde vive lo que atuq NO puede hacer. El guardián baja de verdad (`Content-Disposition: attachment`, no una llamada a la API) dos veces dentro de un mismo compositor, y trae control negativo: con OTRO contenido la segunda vez exige `dedup=false` y dos objetos. Sin él, una sonda que dijera «ya lo tenías» siempre se vería idéntica a una que funciona. Del lado de tawasuyu (`ffa939c6`, pineado acá): `cas.ingest`/`cas.list` en el host y `arje-cas::almacenar_fichero_en` — ingesta en streaming, porque `store` toma `&[u8]` y una ISO de 4 GiB serían 4 GiB de `Vec`. 5 tests nuevos en arje-cas y 5 en puriy-costura. Y EL BUG QUE EL GUARDIÁN DESTAPÓ, que vale más que la función: **hay un host por PUERTO, no uno por perfil.** Con `sct` y `descargas` hablando hay dos procesos vivos a la vez, cada uno con su copia en memoria del estado, y cada uno escribía el fichero ENTERO al guardar: el de descargas borraba el registro TOFU de `sct` al salir, y el de `sct` revertía el índice de descargas. Los dos contestaban bien — el daño estaba sólo en el disco. Se vio porque el guardián lee el índice EN DISCO en vez de creerle a la respuesta: decía `veces=2` y el fichero decía 1. Arreglado en tawasuyu (`55b918e8`, pineado acá): cada proceso escribe sólo la parte que tocó, y el test que lo fija se comprobó ROMPIENDO el arreglo a propósito — porque la primera versión de ese test abría los hosts en secuencia y pasaba con el bug puesto.
hammer
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 comparta el sistema.
hammer no es otro gestor de paquetes inmutable. Es una arquitectura de dos mundos:
- El laboratorio compila desde fuentes upstream (commits fijados), de forma
determinista y aislada (
bubblewrap+zig cc+ musl estático), y direcciona cada artefacto por su hash (BLAKE3) en un content-addressed store. - El userland es un Linux clásico, mutable, con FHS de verdad (
/bin,/lib,/etc). Los binarios se hidratan desde el store por hardlinks. Puedes pisar archivos en caliente, romper cosas con unrm -rf, y revertir cuando quieras.
Entre ambos mundos viven las tres piezas que hacen a hammer distinto:
- Overlay de experimentación — prueba cambios sobre el sistema real con red de
seguridad;
hammer try/commit/discard. - Diario de mutaciones — un daemon
fanotifyregistra todo lo que tú (o la IA) cambiáis a mano. Vives imperativamente; el sistema genera el delta contra la base limpia. El diario es tu configuración — sin ser declarativa. - Manifiesto
.swm— compartes la receta de la mutación (parche de fuente + flags + ediciones de config), no el binario cocinado. El receptor reproduce y verifica; no confía en tu binario.
Encima de todo corre el bus de agente (/run/agent.sock): la IA habla un protocolo
pequeño y legible, dispara compilaciones, inyecta en el overlay, escucha fallos y reacciona.
Tú tienes la última palabra.
Estado
Fase de arranque. Validamos la capa AI-nativa sobre Alpine (musl + FHS ya cocidos)
antes de bajar a distro propia. Ver docs/10-roadmap.md.
Documentación de diseño (SDD)
Toda la arquitectura está en docs/. Empieza por
docs/00-vision.md y docs/01-architecture.md.
Estructura
crates/
hammer-core tipos compartidos: Recipe, Swm, hashing CAS, store
hammer-build el laboratorio: sandbox + compilación + hidratación
hammer-cli el binario `hammer` (build, hydrate, try, commit, apply, export…)
hammerd daemon: bus de agente + diario de mutaciones
docs/ SDDs y ADRs
Stack
| Capa | Decisión |
|---|---|
| Base de validación | Alpine (musl + FHS mutable) |
| Tooling / daemons | Rust (estático-musl) |
| Compilador del lab | zig cc por defecto, pluggable por receta |
| Sandbox de build | bubblewrap (namespaces) |
| Direccionamiento | BLAKE3 content-addressed store |
| Despliegue | Hidratación por hardlinks + patchelf |
Filosofía
La automatización es mi empleada en el sótano, pero en el piso de arriba mando yo.
Eficiencia matemática en la manufactura, libertad biológica en la ejecución.