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 1411Shell 141Markdown 84Rust 832898 Definiciones

docs: BRIEFING-hammer — informe de arquitectura completo para un agente externo

Reúne en un solo documento lo que hoy está repartido en 24 SDDs, 13 ADRs, los
runbooks y el estado generado: arquitectura de dos mundos, definiciones canónicas
(Recipe/hash_inputs/LabFingerprint/.swm/slots), el lab y sus fases, harkaq, el
bucle agéntico, bootstrap auto-alojado, release engineering, arranque por grafo,
los cuatro escritorios, la infraestructura de granja y la metodología yupana/khipu.

Incluye estado MEDIDO al 2026-08-28 (no heredado de los docs) y explica por qué la
deuda del corpus marca 75 y no las 12 del SDD 20: es la cola alfabética pendiente
de la reconstrucción disparada por el lab en hash_inputs y el split de debug.

Cierra con los huecos donde un SDD nuevo tendría tracción.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LykB7vw38b4Ck1Zij15RQ4

2e706bde8b82693e7cb45d40d0494ac2234c0a40 · por Sergio · (hace 1 mes) · padre 2092183d6 · ver el árbol en este commit

Delta semántico

Ninguna definición cambió: el cambio es de texto, documentación o datos.

1 archivos · +915 −0

diff --git a/docs/BRIEFING-hammer.md b/docs/BRIEFING-hammer.md
new file mode 100644
index 00000000..fcc9928f
--- /dev/null
+++ b/docs/BRIEFING-hammer.md
@@ -0,0 +1,915 @@
+# hammer — informe de arquitectura completo (briefing para un agente externo)
+
+> **Para qué es este documento.** Dar a un modelo que NO tiene acceso al repositorio el contexto
+> suficiente para razonar sobre hammer al nivel de proponer SDDs nuevos: arquitectura, capas,
+> componentes, invariantes, decisiones tomadas, estado real medido, deuda abierta y las lecciones
+> de método que este proyecto pagó caro.
+>
+> **Fecha de corte:** 2026-08-28. Todo número marcado *(medido)* salió de un comando corrido ese
+> día contra el repo; los demás llevan la fecha del documento del que vienen.
+>
+> **Idioma y convención:** el repo entero está en español, incluidos commits, docs y comentarios.
+> Los SDD son documentos de diseño numerados (`docs/NN-*.md`); los ADR son decisiones
+> (`docs/adr/NNNN-*.md`). Cuando el código y el SDD discrepan, o se corrige el código o se
+> actualiza el SDD con un commit que explica por qué.
+
+---
+
+## 0. Resumen ejecutivo en diez líneas
+
+`hammer` es una **distribución Linux + su gestor de construcción**, escrita en Rust, cuya tesis es
+separar dos mundos hoy fusionados:
+
+- **El sótano (el laboratorio):** compilación determinista, hermética y direccionada por contenido
+  (BLAKE3). `bubblewrap` + `zig cc` + musl. Cada artefacto es un árbol de ficheros sellado bajo su
+  hash de **entrada**.
+- **El piso de arriba (el userland):** un Linux clásico, mutable, con FHS real (`/bin`, `/lib`,
+  `/etc`). Los binarios se **hidratan** desde el store por hardlinks.
+
+Entre ambos viven tres piezas propias: **overlay** de experimentación (`try`/`commit`/`discard`),
+**diario de mutaciones** por `fanotify` (la configuración se deriva *a posteriori*, no se declara
+antes), y el **`.swm`**, un manifiesto de mutación que viaja en vez del binario — el receptor
+**reproduce y verifica**. Encima corre un **bus de agente** (`/run/agent.sock`) por el que una IA
+propone, construye, prueba en overlay y presenta; el humano hace `commit`.
+
+El proyecto **ya cerró** el bucle AI-nativo completo, el bootstrap auto-alojado bit-reproducible
+(incluido el toolchain y el kernel), cuatro escritorios construidos desde fuente, formato de
+paquete/repositorio/instalador, imágenes ISO/USB/EFI e infraestructura de granja de compilación.
+Lo que falta para publicar es sobre todo legal, de claves y de validación en metal.
+
+---
+
+## 1. Filosofía y anti-objetivos (SDD 00)
+
+**El problema.** Las distros inmutables/declarativas (NixOS, Guix, Silverblue) dan determinismo a
+costa de agencia: el SO *es* el grafo, se pierde la estructura familiar y la IA tiene que simular a
+un humano tras capas de abstracción. Las tradicionales (Arch, Gentoo) dan control pero se pudren:
+el gestor pierde el rastro de lo que el usuario hizo a mano.
+
+**La tesis.** *«La automatización funcional es mi empleada en el sótano. En el piso de arriba mando
+yo.»* Determinismo en la fábrica, libertad en la ejecución; ninguno de los dos se negocia.
+
+**Por qué es AI-nativo** (el diferenciador real, no la distro en sí):
+1. Compilación determinista ⇒ la IA cambia fuente, recompila, y el binario es predecible.
+2. El sistema ejecutable es **texto plano + binarios atómicos** ⇒ nada de bases de datos binarias
+   opacas (registro, journal binario). Se lee con `cat`, `grep`, `readelf`.
+3. Mutabilidad = agencia real, con overlay como red de seguridad.
+4. **musl** mantiene el C legible para el contexto de un LLM (glibc es un laberinto de macros).
+5. El enlazado estático evita que mutar una herramienta rompa las librerías de otra.
+
+**Principios.** Verificar, no confiar · texto plano por encima de bases opacas · la IA propone, el
+humano dispone · no reinventar lo resuelto · **el diario es la verdad** (se vive imperativamente y
+el estado declarativo se deriva después, nunca al revés).
+
+**Anti-objetivos.** No es un gestor inmutable; no reescribe un motor de build (ADR 0004: **hammer
+NO usa nix**); no compila contra `HEAD` vivo (ADR 0006); no oculta complejidad tras abstracciones.
+
+---
+
+## 2. Arquitectura general (SDD 01)
+
+### 2.1 Modelo de dos mundos
+
+```
+   EL SÓTANO (laboratorio)                    EL PISO DE ARRIBA (userland)
+   determinista · hermético                   mutable · clásico · FHS real
+
+   upstreams (commits/tarballs FIJADOS)
+        │
+   hammer-build (bwrap + zig cc)   ──sella──▶  CAS store  ──hidrata──▶  /bin /lib /etc
+   recetas + grafo de deps                    /store/<b3>-<name>/       (hardlinks)
+                                                   │                        │
+                                                   ▼                        ▼
+                                            overlay try/commit      diario fanotify
+                                                   │                        │
+                                     BUS DE AGENTE /run/agent.sock (JSON-líneas)
+                                     COMPILE · INJECT · QUERY · eventos
+                                                   ▼
+                                            [ IA programadora ]
+```
+
+**Regla de oro:** los dos mundos nunca se mezclan. El lab jamás escribe en el FHS real (pasa por
+store + hidratación); el userland jamás compila contra el host (delega al lab).
+
+### 2.2 Los crates (workspace Rust, edición 2021, `opt-level="z" + lto + panic=abort`)
+
+| Crate | Responsabilidad única |
+|---|---|
+| `hammer-core` | Tipos compartidos: `Recipe`, `Swm`, `Store`, `ArtifactHash`, `proto` (bus), `repo`, `sign`, `installed`, `compat` (slots), `query`, `differs`, `lab`, `apply`, `caps`, y el módulo `kernel/` (SDD 22) |
+| `hammer-build` | El laboratorio: `sandbox` (bwrap), `fetch`, `download`, `hydrate`, `swm_bridge`, `harkaq` (la jaula), `config` |
+| `hammer-bootstrap` | Orquesta stage0/stage1/stage2/builder/all + `manifest` (log de transparencia) |
+| `hammer-overlay` | `try`/`commit`/`discard`/`status` sobre overlayfs, con apilamiento LIFO |
+| `hammer-journal` | Diario append-only JSON-líneas, `content_hash`, de-dup idempotente |
+| `hammer-mirror` | Replicación del store por hash entre máquinas (verifica `of_tree` al recibir) |
+| `hammer-upgrade` | Generaciones, apply/rollback/prune/recover, `boot_graph` (ADR 0010) |
+| `hammer-recover` | Mini-binario estático que auto-sana un upgrade interrumpido, al arranque |
+| `hammer-agent` | `AgentClient`, `IntentTranslator` (+ `MockTranslator`, `ClaudeTranslator`), `Orchestrator` |
+| `hammer-cli` | El binario `hammer` (+ `alpine_import`, `nix_import`, `kernel_cmd`) |
+| `hammerd` | Daemon: bus de agente, watcher fanotify, FIFO de init, `crashes`, `arje_link` |
+| `netup` | DHCP/netlink mínimo para el producto |
+
+### 2.3 Estado en disco
+
+```
+/store/<blake3>-<name>/     artefacto sellado, inmutable, append-only
+/var/lib/hammer/
+  recipes/  pins.toml  journal/  overlays/  trust/  installed.json  upgrades/
+/run/init.control           FIFO de control humano (texto crudo)
+/run/agent.sock             socket de agente (JSON-líneas, SO_PEERCRED)
+/run/hammer/boot-graph.json grafo de estados que dibuja el menú de arranque (ADR 0010)
+```
+
+---
+
+## 3. Definiciones canónicas (lo que hay que entender para proponer cambios)
+
+### 3.1 `Recipe` — el átomo del grafo
+
+TOML declarativo y puro. Campos reales (`hammer-core/src/recipe.rs`):
+
+```toml
+name = "grep"
+version = "3.12"
+license = "GPL-3.0-or-later"     # FUERA de hash_inputs (ver §3.2)
+
+[source]
+repo = "…" ; commit = "…"        # git  … XOR …
+tarball = "…" ; sha256 = "…"     # tarball
+strip_components = 1
+patches = ["…patch"]
+cargo_vendor = true              # perilla del vendoreo Cargo
+cargo_vendor_dir = "…"           # cuando el proyecto ya trae su vendor/
+
+[build]
+compiler = "zig-cc"              # "zig-cc" | "clang" | "gcc"
+target   = "x86_64-linux-musl"
+link     = "static"              # "static" | "dynamic"
+flags    = [...]
+zig_version = "0.13.0"           # pin opcional (84 recetas lo fijan)
+strip_debug = true               # separa .debug_* (SDD 23) — SÍ entra al hash
+cgo = false
+subdir = "…"
+[build.phases] configure/compile/install  # overrides opcionales
+
+[deps] build = [...] ; runtime = [...]
+[evidence] checks = [...]        # H1 proof-carrying (fuera del hash)
+[slots] claims = {...} ; requires = {...}   # H4 compatibilidad (fuera del hash)
+```
+
+### 3.2 `ArtifactHash` — hash de **entrada**, no de salida
+
+```
+artifact_hash = BLAKE3(
+      source_id            // "git:<commit>" XOR "tarball:<sha256>"
+    ⧺ compiler ⧺ target ⧺ link
+    ⧺ lab:<LabFingerprint>          // ← desde 2026-08-10
+    ⧺ [zig:<version>]  si está fijado
+    ⧺ [strip_debug:<bool>] si está fijado
+    ⧺ contenido de cada patch
+    ⧺ cada flag
+    ⧺ phase:configure/compile/install si hay override
+    ⧺ Σ dep.artifact_hash )
+```
+
+Consecuencias que gobiernan casi todas las decisiones del proyecto:
+
+- **`hash_inputs` es una LISTA BLANCA.** Lo que no está enumerado no mueve el hash. Por eso
+  `license`, `evidence` y `slots` se pudieron añadir **sobre recetas ya selladas** sin re-hashear
+  nada. Es el truco recurrente para pagar deuda barata.
+- **Las fases de build SÍ entran.** ⇒ editar el `-j` de una fase cambia el hash aunque el binario
+  sea idéntico; ⇒ el `.config` del kernel (que vive en la fase `configure`) **es la identidad del
+  artefacto** (SDD 22 §1); ⇒ `strip_debug` obliga a re-hashear el corpus (SDD 23).
+- **El hash es de entrada, no de contenido.** `hammer hash <receta>` responde en ~2 ms cuál es el
+  hash VIGENTE sin construir; `--check` dice si ya está sellado. `ArtifactHash::of_tree` es la otra
+  cosa: el hash del **contenido** real de un árbol, usado para verificar reproducibilidad y para el
+  mirror.
+- **Corolario peligroso:** `strip` posterior al sellado rompería la verificación bit-a-bit, porque
+  el hash de entrada seguiría apuntando a algo que un rebuild ya no reproduce.
+
+### 3.3 `LabFingerprint` — el toolchain como entrada (2026-08-10)
+
+Se computa del **apk db del rootfs real** que va a compilar (no de un fichero declarado). Entran
+compiladores/enlazadores (gcc, clang, llvm, binutils, rust, cargo), las libs de codegen de gcc
+(gmp, mpfr, mpc, isl), el runtime que se enlaza dentro (musl, libgcc, libstdc++, libatomic,
+libgomp) y los headers que se compilan dentro (linux-headers, fortify-headers). Quedan fuera **a
+propósito** autotools y shells: es una decisión de coste, no una afirmación de que no influyen.
+
+**Por qué existe:** reconstruyendo los cuatro kernels en un segundo hub, el `.config` instalado salió
+distinto en 4 líneas (`CONFIG_RUSTC_VERSION 109600→109700`) — Rust 1.96 vs 1.97 del rootfs, y **Rust
+ni siquiera estaba activado en ese kernel**. Antes de esto, dos labs sellaban bytes distintos en la
+MISMA dirección y el store no podía notarlo.
+
+### 3.4 Store, hidratación, overlay, diario
+
+- **Store**: inmutable, append-only, deduplicado, GC por alcanzabilidad (`scripts/store-gc.sh`
+  distingue SUPERADO —hay ejemplar mejor— de HUÉRFANO —ejemplar único—). `Store::has` rechaza
+  directorios vacíos (un vacío es un nombre, no un artefacto).
+- **Hidratación** (ADR 0005): hardlink directo en el caso estático; en el dinámico se **copia** y
+  se reescribe con `patchelf --set-interpreter/--set-rpath` (no se hardlinkea, o patchelf mutaría
+  el inode del store). Escribir encima rompe el hardlink ⇒ el store queda intacto como base de
+  rollback.
+- **Overlay** (SDD 04): overlayfs con `upperdir` mutable sobre el FHS real. `try`/`commit`/
+  `discard`/`status`. El apilamiento es total y determinista (`created_at` + `id` + `seq`) y
+  `commit`/`discard` exigen **LIFO** (`Error::Shadowed` si una capa más joven sombrea el target).
+- **Diario** (SDD 05): `fanotify` (FAN_CLOSE_WRITE) sobre `/bin`,`/sbin`,`/usr/bin`,`/usr/sbin`,
+  `/lib`,`/usr/lib`,`/etc`; JSON-líneas append-only; `op ∈ {Create, Edit, Delete}` derivado por
+  `classify_op`; `content_hash` tras la mutación con de-dup idempotente. Distingue mutación
+  **trazable** (vino de un artefacto conocido) de **opaca** (pisada a mano) sin prohibir ninguna.
+
+### 3.5 `.swm` — la unidad de intercambio (SDD 06)
+
+YAML con `base` (distro_version + pins), `mutations` y `signature` opcional (Ed25519). Cuatro tipos
+de mutación: `source_patch` (una `Recipe` serializada para viajar), `config_edit` (hunks exactos,
+sin fuzz), `init_rule` y `file_drop` (`content_b64` XOR `content_url`, siempre con `content_hash`
+BLAKE3 verificado **antes** de escribir nada).
+
+`hammer apply` verifica base → verifica firma → **reproduce** en el lab local → aísla en overlay →
+el humano valida → `commit`. **Nunca se ejecuta un binario ajeno.**
+
+### 3.6 Repositorio de paquetes (Etapa F)
+
+Un repo son ficheros estáticos: `index.json` + los `.swm`. Un `.swm` no tiene identidad propia; la
+identidad la asigna el índice al publicar (**el índice es el namespace**). `install <nombre>`
+resuelve el **cierre transitivo** de build-deps del índice (topológico), sintetiza un catálogo de
+recetas efímero desde los `.swm` de las deps y reproduce desde fuente. El índice entero se firma
+como **release** (cualquier `pack --repo` la invalida ⇒ re-firmar). Consumible por HTTP(S).
+
+---
+
+## 4. El laboratorio de build (SDD 02)
+
+### 4.1 El sandbox
+
+`bubblewrap` con `--unshare-all` (net, pid, mount, ipc, uts, user). Base y deps montadas
+read-only con `--overlay-src` + `--tmp-overlay /`; las únicas superficies RW son `/src` (árbol de
+fuentes, bind) y `/out` (`DESTDIR`). Entorno mínimo y fijado: `SOURCE_DATE_EPOCH`, `CC`,
+`-mcpu=baseline`, `CARGO_BUILD_JOBS=1`, `CARGO_PROFILE_RELEASE_CODEGEN_UNITS=1`.
+
+**Sin red dentro del sandbox.** El `fetch` ocurre fuera. Corolario que aparece una y otra vez: el
+build es *offline por construcción*, así que su clausura **no contiene** las clases de servicio del
+mundo (DNS, TLS, syslog…).
+
+### 4.2 Fases
+
+| Fase | ¿Sandbox? | Qué hace |
+|---|---|---|
+| `fetch` | No (red) | Clona al commit / baja el tarball y verifica sha256; vendorea Cargo/Go |
+| `patch` | Sí | Aplica `source.patches` |
+| `configure` | Sí | Heurística de build system: autotools / cmake / meson / cargo / make plano |
+| `compile` | Sí | Compila al `target`/`link` de la receta |
+| `install` | Sí | `make install DESTDIR=/out` o equivalente |
+| `seal` | No | Hashea `/out` y lo deposita en el store |
+
+**Materialización de build-deps:** cada `deps.build` se construye recursivamente y su artefacto se
+apila como capa `--overlay-src` BAJO el rootfs del sandbox, dejando sus `usr/{include,lib,
+lib/pkgconfig}` en `/usr`. Así pkgconf y zig cc los encuentran sin plumbing de flags, y las recetas
+sin deps quedan byte-iguales (baseline intacto).
+
+### 4.3 Poliglotismo
+
+- **C/C++**: `zig cc` por defecto (ADR 0003: un binario = compilador + musl + headers, hermético,
+  cross). Escotilla `compiler = "gcc"` para gcc-ismos (hoy ~16 recetas).
+- **Cargo**: triple traducido (`x86_64-linux-musl` → `x86_64-unknown-linux-musl`), wrapper `zig cc`
+  como `CARGO_TARGET_<triple>_LINKER` (cargo no acepta linker de dos palabras), `--release --locked
+  --offline` con `vendor/` obligatorio. `link = "dynamic"` añade `-C target-feature=-crt-static`
+  (necesario para `dlopen` de Vulkan/Wayland).
+- **Go**: 362 recetas *(medido)*; el `go mod vendor` ocurre en el host, antes del sandbox.
+
+### 4.4 harkaq — la jaula (SDD 16)
+
+**Tesis:** bwrap da hermeticidad *respecto del host*, pero monta un **rootfs Alpine entero** como
+capa base ⇒ `política ⊋ clausura`: una receta puede usar cualquier header o binario de Alpine sin
+declararlo y el build pasa en verde. harkaq cierra esa brecha: **la clausura de deps declaradas ES
+la política** (Landlock + seccomp), y el producto es **evidencia negativa**: `hermético ⟺ política =
+clausura ∧ denegaciones = ∅`.
+
+Decisiones cerradas: **D1** política derivada, no escrita · **D2** evidencia negativa (por eso es
+barata) · **D3** cero quieting (las denegaciones SON el producto; ABI 10 trae `ADD_RULE_QUIET` y se
+decide deliberadamente no usarlo) · **D4** seccomp obligatorio, no opcional · **D7** fallo cerrado y
+honesto sobre el ABI (kernel sin Landlock ⇒ build marcado *sin evidencia*, nunca fingir) · **D9** el
+**canario deliberado**: `denials=[]` no se cree, se gana (se antepone un acceso que DEBE ser
+denegado; si no aparece, el lector está roto).
+
+Estado: Fase 0 y Fase 1 cerradas (`zlib` real: `configure→Hermetico`, `compile→Impuro` por
+`/usr/bin/make` no declarado); wiring en `hammer-build/src/harkaq.rs` detrás de `HARKAQ=1` e
+**inerte** sin él (requisito duro: 700+ artefactos sellados no pueden cambiar de hash por encender
+un diagnóstico). Barrido en granja: **0 irreducibles** ⇒ la meta de <5% de deuda se cumple.
+Sutileza medida: lo que DAC ya bloquea es invisible a Landlock (el hook no llega a correr) — inocuo
+para el veredicto, trampa para escribir tests.
+
+**Regla operativa aprendida:** el worker MIDE, el hub CLASIFICA (un store parcial infla la métrica).
+
+---
+
+## 5. El bucle agéntico (SDD 07, 08)
+
+### 5.1 Dos canales
+
+- `/run/init.control` — FIFO de control **humano**, texto crudo (`echo "restart network" > …`).
+- `/run/agent.sock` — socket de **agente**: JSON-líneas, handshake `Hello/Welcome`, auth por
+  `SO_PEERCRED`, capacidades leídas de `/etc/hammer/agent-caps.toml` (`default` + `[[rule]]` por
+  uid/gid, primera que casa gana).
+
+Comandos: `Compile`, `Inject`, `Query`, `Init`. Eventos: `BuildReady`, `BuildFailed` (con
+`log_tail` real de 50 líneas del compilador), `Injected`, `QueryResult`, `InitAck`, `Modified`,
+`Crashed`, `Error`.
+
+**Capacidades mínimas:** por defecto un agente sólo obtiene `query`. `compile`, `inject` (a overlay)
+e **`inject-real`** (al FHS) se conceden por separado; `inject-real` exige confirmación humana por
+política.
+
+### 5.2 El bucle
+
+```
+intención NL → PLAN (.swm) → BUILD (lab) → TRY (overlay) → VERIFY (tests + evidence)
+             → PROPOSE (diff + evidencia) → HUMANO decide commit/discard
+```
+
+`hammer ai <intent> --catalog F | --llm [--repair-max-attempts N]`. El traductor está detrás del
+trait `IntentTranslator`: `MockTranslator` (catálogo YAML intent→`.swm`, para CI byte-a-byte) y
+`ClaudeTranslator` (feature `llm-claude`, ureq+rustls). `Orchestrator::run_with_repair` drena
+eventos del bus tras cada apply; si llega `Crashed`, formatea una intención nueva y reentra hasta
+`max_attempts`, y el `Proposal` final lleva la `repair_chain` completa.
+
+**Mini-lenguaje de consulta** (`hammer query kind:value`): `bin`, `file`, `pin`, `service`,
+`depends` — con parser ELF64 mínimo para extraer `DT_NEEDED`.
+
+---
+
+## 6. Modelo de confianza (SDD 09)
+
+```
+fuente pública (repo@commit fijado) + patch declarado
+        → build determinista en TU lab local → artifact_hash
+        → ¿coincide con el expected_hash del autor?
+             sí → obtuviste su binario sin descargarlo
+             no → algo difiere (pin, patch, toolchain); hammer dice qué
+```
+
+La firma Ed25519 cubre **autoría e integridad**, no autoriza promover: `apply` **siempre** reproduce
+y compara, firme o no. TrustStore local en `/var/lib/hammer/trust/*.ed25519.pub`; estados
+`trusted`/`unknown-key`/`bad-sig`/`unsigned`. El **log de transparencia** es opt-in y su embrión ya
+existe: `bootstrap.json` (SDD 11 §4).
+
+Requisitos prácticos de reproducibilidad: toolchain fijado (ya en el hash), commits fijados, sandbox
+hermético, y eliminación activa de no-determinismo. **Cuando un build no reproduce es un bug a
+corregir, no una excepción a tolerar.**
+
+---
+
+## 7. Bootstrap y auto-alojamiento (SDD 11, 12; ADR 0007, 0008) — **CERRADO**
+
+```
+host (sólo kernel + semilla pinned)
+ └─ Stage 0: toolchain semilla (zig) ingerido al store, sha256 fijado   ✅
+ └─ Stage 1: userland mínimo cross-compilado (musl, busybox, hammerd, arje-zero)  ✅
+ └─ Stage 2: rebuild NATIVO dentro del rootfs Stage 1 y comparación de of_tree     ✅
+```
+
+Hitos verificados:
+
+- **Stage 1 bootea en QEMU** con **arje-zero como PID 1** (init propio, del monorepo hermano
+  `tawasuyu`) leyendo su seed card `/ente/seed.card.json` y supervisando hammerd + getty. El
+  **`CRASHED` real** que la Fase 5 había diferido está demostrado: `kill hammerd` → arje detecta
+  `Killed(SIGTERM)`, aplica backoff y re-encarna.
+- **Stage 2 `✓ REPRODUCIBLE`** host↔VM: `of_tree(stage1') == of_tree(stage1)`.
+- **Auto-alojamiento puro (variante b) CERRADO**: el toolchain del builder lo construye hammer desde
+  fuente. Los **6 swaps** (`make`, `busybox`, `coreutils`, `bwrap`, `linux-headers`, `rust`)
+  corrieron **juntos in-VM** dando `✓ REPRODUCIBLE` (`of_tree = 7fa6cb4e…`). La cadena de Rust es
+  purista: `mrustc → rustc 1.90.0 → 1.91.0 → 1.91.1` con `x.py` real, reusando un LLVM 20.1.8
+  externo.
+- **Kernel from-source CERRADO**: Linux 6.16.12 construido por hammer bootea la VM del
+  selfhost-verify y reproduce bit a bit. `flex`, `bison`, `openssl`, `elfutils` (sólo libelf, para
+  objtool) de-Alpinizados como build-deps del kernel.
+
+**Los dos no-determinismos que hubo que cazar** (valen como lección general):
+1. `codegen-units=1` **NO bastaba**: el backend paralelo de rustc/LLVM (ThinLTO) divergía ~62 KB en
+   `arje-zero` a >1 CPU. Fix: `CARGO_BUILD_JOBS=1` serializa el jobserver ⇒ reproducible e
+   independiente del nº de CPUs.
+2. El baseline "bueno" anterior (`0039b2b9…`) era un build paralelo no fiable. **Dos números
+   iguales de un fallo son señal, no coincidencia**; y un baseline no verificado es una foto vieja
+   con aspecto de dato fresco.
+
+---
+
+## 8. Release engineering (SDD 13) — imágenes, instalador, mirror, upgrades
+
+- **E1 imagen auto-booteable** (GRUB BIOS i386-pc, instalado en userspace sin root con
+  `grub-mkimage` + `grub-bios-setup` sobre el fichero imagen). Layout GPT: BIOS-boot / `/` /
+  `/store` / `/var/lib/hammer`.
+- **E2 instalador a disco físico**: `hammer-install <dev>` corre dentro del live como root real
+  (MBR, ext2 montable como ext4, GRUB por dos `dd`). Autoinstalador: copia la propia raíz del live.
+- **E3 mirror del store** (`hammer mirror push|pull|status`): CAS replicado; el receptor
+  **recomputa `of_tree`** y exige que case antes de sellar ⇒ una transferencia corrupta se rechaza.
+- **E4 upgrades con generaciones**: apply in-place fichero-a-fichero (escritura a temporal +
+  `rename`), `current` como commit-point, `backup/` con los bytes previos, rollback exacto,
+  `prune`, y **`pending.json`** escrito ANTES de proyectar para que `recover` complete
+  (roll-forward) o deshaga (roll-back). `hammer-recover` (binario estático) lo hace **al arranque**,
+  antes de encarnar a arje-zero, y nunca aborta el boot.
+- **E5 ISO/medio live**: híbrido BIOS+UEFI (dos El Torito), initramfs = product-rootfs `cpio.gz`,
+  todo desde RAM. **Dogfooding**: el host no traía `xorriso` ni `mtools` ⇒ los construye hammer.
+- **Arranque soberano EFI-stub** sin GRUB (ADR 0010), con cero parpadeo validado por captura de
+  framebuffer.
+
+---
+
+## 9. Arranque por grafo (ADR 0010) — la frontera con `tawasuyu`/mirada
+
+El menú de arranque **no es una lista de kernels**: es la **navegación del grafo de estados** del
+sistema. hammer publica `/run/hammer/boot-graph.json` (nodos = generaciones/snapshots/base/recovery,
+identificados por BLAKE3, con `parents`, `bootable`, `label`); **mirada** (compositor Wayland de
+tawasuyu) lo dibuja sobre KMS y escribe la elección en `/run/hammer/boot-select`; arje-zero (PID 1)
+corre `hammer boot activate --from-select` **incondicionalmente en su secuencia de apagado** — `/run`
+es tmpfs, así que la selección se aplica en el apagado o no se aplica nunca.
+
+Invariante de UX compartido: **cero parpadeo** de DRM desde la firmware hasta el escritorio (mirada
+sostiene el DRM master y nunca re-modesetea; por eso mirada real **nunca sale** y el subcomando
+`hammer boot menu` es sólo harness de dev).
+
+---
+
+## 10. El armador de kernel (SDD 22) — implementado
+
+Verbo `hammer kernel {stats, probe, bundles, closure, plan, diff-back, gate, hw}`.
+
+Los dos hechos que ordenan el diseño:
+
+1. **El config ES la identidad del artefacto** (vive en la fase `configure`). A favor: un "config
+   atestado por huella de hardware" sale casi gratis (es un `ArtifactHash` + una firma). En contra:
+   la explosión combinatoria es aritmética visible en el disco ⇒ **una perilla de la UI no es un
+   parámetro de runtime, es una edición de receta**, y `kernel plan` debe **emitir una receta
+   derivada**.
+2. **La clausura por `depends on` NO cierra: `select` es el portillo.** Kconfig tiene al menos tres
+   tipos de arista (`depends on` hacia arriba, `select` hacia abajo y forzada, `imply` blanda) con
+   semánticas incompatibles. Hay corpus de verdad-de-campo contra el que validar: el `configure` de
+   `linux.toml` **ya es** un juego de bundles escrito a mano (`-d WLAN -d WIRELESS -d CFG80211…`) y
+   ese kernel arranca.
+
+Añadidos propios del SDD: **bisección sin recompilar** (kernel superset con lo bisecable como `=m`
+⇒ 6 reinicios en vez de 6 rebuilds × 45 min) y **gate de no-regresión por objetivo**, no global (el
+kernel de QEMU apaga USB/HID a propósito; un gate global rechazaría una receta sana).
+
+Probado de punta a punta el 2026-08-11: kernel a medida de `gioser`, bzImage **11,1 MB vs 14,5
+(−23%)**. Y el lab está literalmente DENTRO del artefacto: Kconfig sondea el `rustc` del entorno y
+graba `CONFIG_GCC_VERSION`/`CONFIG_RUSTC_VERSION` en el `.config`.
+
+---
+
+## 11. Los escritorios (cuatro imágenes, todas desde fuente)
+
+| Frente | Estado | Notas clave |
+|---|---|---|
+| **mirada** | perfil 33/33 *(medido)* | Compositor Wayland propio (tawasuyu) + greeter, sobre mesa software o iris |
+| **KDE Plasma 6** | perfil `escritorio-kde` **163/163** *(medido)* | Hidratado rebuild-free vía `hammer hydrate <hash> --into` (install-reproduce no escala). ADR 0011 |
+| **GNOME** | perfil 124/127, 3 aparcadas por diseño *(medido)* | Keystone: el shell es una **isla dinámica** — gjs `dlopen`ea `.so` reales ⇒ la introspección los exige. `mutter→gnome-shell` es HUB-ONLY estructural |
+| **COSMIC** | perfil 89/90 *(medido)* | 4º escritorio usable, 6 apps + panel/dock, portal cerrado (captura da PNG real). Barato: no hay torre de C debajo |
+| **wlr/sway** | perfil `escritorio-sway` **129/129** *(medido)*, arranca en QEMU | 10 recetas en una noche: wlroots, sway, yambar, fuzzel, swaybg/lock/idle, grim, slurp, wl-clipboard |
+
+**Stack gráfico (SDD 14):** Mesa **24.0.9** iris-only, sin LLVM y sin Vulkan. El pin es duro: desde
+24.1 `iris` exige `intel-clc` ⇒ libclc + clang/LLVM. Debajo: libdrm, wayland (+ el parche
+`wayland-scanner-dup2-output` — bajo musl dinámico `freopen(@OUTPUT@)` trunca el header por la
+copy-relocation de `stdout`), wayland-protocols, seatd, libinput/libevdev/mtdev/libudev-zero,
+libxkbcommon, pixman.
+
+**La lección transversal de este bloque, y probablemente la más importante del proyecto:**
+
+> **Sellado ≠ arranca.** El perfil `escritorio-sway` cerró **121/121, falta 0** — y entre eso y una
+> pantalla con algo dibujado hubo **seis muros y ocho ciclos de imagen**: `PATH` vacío, `libz.so.1`
+> ausente del rootfs, un backend de libseat que nuestra receta no construye, `/run` de sólo lectura,
+> los datos XKB ausentes, y falta de tipografías. **Ninguno era una dependencia de build**, así que
+> ninguno podía aparecer en el grafo. Y el método: **el veredicto es contar colores del PPM, no leer
+> el log** — hubo un arranque con todo verde en el log y la captura 100 % negra.
+
+---
+
+## 12. Infraestructura operativa (lo que no está en los SDD pero gobierna el día a día)
+
+### 12.1 Topología
+
+- **Hub principal**: laptop del autor + un segundo hub `gioser.net` (server Hetzner **protegido, sin
+  backup**, mismo proyecto hcloud que la granja). El store vive en un **volumen** dedicado
+  (bind-mount de `store`, `work/sources`, `work/repos`, `CARGO_HOME`).
+- **Granja efímera**: workers hcloud on-demand (`farm-up` / `farm-run` / `farm-down`, baseline €0),
+  arrancados desde un snapshot **golden** que trae el toolchain + el store horneados ⇒ cache-hit
+  instantáneo. El worker es **compute puro y sin secretos** (ni firma ni gitea). Lleva un *dead-man*
+  que lo **borra** (no lo apaga — apagado seguiría facturando) tras 1 h idle.
+- **Latido**: un cron cada 30 min cosecha el worker, siembra recetas y regenera el grafo. Los
+  commits `estado: cosecha granja <ts>` del historial son ese latido.
+- **Respaldo**: Storage Box BX11 (1 TiB), interrumpible y continuable; sube primero el *cerebro*
+  (estado + repo) y después el store.
+- **Espejo**: `origin` empuja a gitea **y** a un espejo privado de GitHub.
+
+### 12.2 El khipu y la yupana — la metodología de medición
+
+Distinción explícita y arquitectónica, no adorno:
+
+- **`docs/state/build-state*.json` = el KHIPU**: el registro firme y versionado (nudos = recetas,
+  cuerdas = deps). Fuente única de verdad de QUÉ hay y QUÉ falta. **Se regenera**
+  (`scripts/build-state.py`); un generado que no se regenera es una foto vieja con aspecto de dato
+  fresco (error real: KDE reportaba 162/162 cuando estaba en 76/162).
+- **`scripts/yupana.py` = la YUPANA**: el motor que reckona sobre el khipu. Es la **puerta única** de
+  la metodología. Verbos: `radio <receta>` (¿a quién rompo si toco esto?), `perfiles`, `duplicados`,
+  `keystones`, `preflight`, `estado`, `objetivo`, `frontera`, `triaje`, `drenar`.
+
+**La regla que la justifica:** *qué nodos puntúo es una decisión de vista; **quién me consume es un
+hecho** y nunca depende de la vista.* Nació de un fallo real: un cambio a `libdrm` invalidó 118
+recetas KDE, y el radio medido sólo contra `recipes/*.toml` dio **8**. Las dependencias inversas se
+calculan SIEMPRE sobre todas las colas.
+
+**Resolución sibling-first**: una dep `libdrm` desde `incoming-kde` resuelve a `incoming-kde/libdrm`
+si existe, y si no cae a `corpus/libdrm` — igual que el sandbox al apilar capas. Por eso los nodos
+se identifican por **par `(cola, nombre)`**, no por nombre.
+
+Otros órganos: `targets.py` (perfiles → lista), `drenar.py` (orden de masticado en ondas),
+`triaje.py`, `seed-graph.py --frontera`, `affected.py` (CVE por grafo), `why-differs.py`,
+`verificar-repro.sh`, `store-gc.sh`, `licencias*.sh`, `fuentes/mirror-*` (ADR 0013).
+
+### 12.3 `targets.toml` — el manifiesto de OBJETIVO
+
+`build-state.json` es el grafo de lo que EXISTE; `targets.toml` es lo que la distro **debe** tener.
+La inversión clave: `paquetes` lista **sólo las raíces** (lo que un usuario pide por nombre); la
+clausura la calcula el grafo. Siete perfiles: `base`, `cli`, `escritorio-mirada`, `escritorio-kde`,
+`escritorio-gnome`, `escritorio-cosmic`, `escritorio-sway`. Un `perfil` = una imagen enviable;
+`hereda` compone; `cola` dice en qué árbol viven sus raíces.
+
+### 12.4 ADR 0013 — mirror de fuentes (obligación GPL, y sale barato)
+
+**La URL no entra en `hash_inputs`; sólo el `sha256`/commit** ⇒ los mirrors son gratis. Cerrado:
+tarballs por sha256 (1,7 G) + git por bundle shallow (570/571, 2,3 G). Detalles pagados: 4 pines no
+son commits sino objetos **tag** (hay que pelar con `^{commit}`); `github.com` es el 64 % del
+corpus. Regla dura: **nunca cambiar el `sha256` para tapar un 404**. Vigía activo:
+`fuentes-vigia.json` → **1172 fuentes, 1172 vivas, 0 muertas** *(medido)*.
+
+---
+
+## 13. Estado real medido (2026-08-28)
+
+### 13.1 Corpus
+
+| Métrica | Valor *(medido)* |
+|---|---|
+| Recetas `.toml` activas | **1172** (+25 aparcadas en `*/.deferred`) |
+| — corpus (`recipes/`) | 787 |
+| — `incoming-kde` / `-gnome` / `-cosmic` / `-gnome-onda2` / `-wlr` / `-onda1` | 205 / 90 / 47 / 22 / 15 / 6 |
+| Parches `.patch` en el árbol de recetas | 123 |
+| Recetas con campo `license` | **1096 / 1197 (92 %)** |
+| Recetas con `compiler = "gcc"` | **36** (16 en el corpus, 20 en las colas) |
+| Recetas con `strip_debug` (campaña SDD 23) | **35**, todas en el corpus |
+| Recetas que pinean `zig_version = "0.13.0"` | 84 |
+| Entradas en el store local | 1654 (55 G en este disco; el store real vive en el volumen) |
+| Fuentes vivas (vigía) | 1172 / 1172, 0 muertas |
+
+### 13.2 Grafo de estado, por vista
+
+| Vista | nodos | sellados | deuda |
+|---|---:|---:|---:|
+| corpus | 787 | 711 | **75** |
+| KDE (`--kde`) | 978 | 899 | 74 (+4 `wanted`) |
+| GNOME | 861 | 783 | 74 (+4 `never`) |
+| COSMIC | 833 | 757 | 75 |
+| wlr | 801 | 726 | 74 |
+
+Deuda del corpus por clase: **go 37 · rust 26 · c 12 · gui 1**.
+
+**Perfiles** *(medido)*: `base` 52/52 · `cli` 75/75 · `escritorio-mirada` 33/33 ·
+`escritorio-kde` **163/163** · `escritorio-sway` **129/129** · `escritorio-cosmic` 89/90 ·
+`escritorio-gnome` 124/127.
+
+### 13.3 Lectura de la deuda actual (importante para no malinterpretar los SDD 19/20)
+
+Los SDD 19/20 (2026-08-07) hablan de **«12 recetas en deuda»**. Hoy el corpus marca **75**, y **no
+es una regresión de calidad**: la lista de nombres en deuda va alfabéticamente desde
+`llimphi-counter`/`mise` hasta `zola` — es la **cola pendiente de la campaña de reconstrucción del
+corpus en serie** (`scripts/reconstruir-corpus.sh`, reanudable, alfabética), disparada por los dos
+eventos que movieron el `ArtifactHash` de todo el catálogo a la vez:
+
+1. **El lab entró en `hash_inputs`** (2026-08-10, §3.3).
+2. **El split de debug** (SDD 23, `strip_debug`), que cambia la fase `install`.
+
+Y el diagnóstico de «las 12» originales fue **desmontado** después: 6 estaban SUPERADAS por la
+cadena dinámica de GNOME, 1 cerrada (`wlr-randr`: le faltaban `python3` —meson es un script de
+Python— y `libffi`, que exige `wayland-client.pc` en su `Requires`), 1 es frente propio
+(`elfutils-libdw`, ya cerrado: dwarves construye) y 3 son HUB-ONLY estructural (fuente por SSH a
+gitea privado; el worker es sin secretos por diseño). El hallazgo de fondo fue de **encolado, no
+técnico**: `recipes/` (el corpus) no estaba en la lista `QUEUES` del bucle del worker — nadie las
+estaba intentando.
+
+---
+
+## 14. Deuda abierta y problemas sin resolver
+
+### 14.1 ADR 0012 — la carrera del árbol de fuentes (**PENDIENTE, sin decidir**)
+
+`fetch` nombra el árbol de forma determinista y **sin componente por-constructor**:
+`work/sources/<nombre>-<sha16>`. Dos builds de la misma dep resuelven al MISMO directorio; uno hace
+`remove_dir_all` mientras el otro corre `tar -x` y **el árbol queda roto para siempre**.
+
+El árbol cumple **dos papeles que se contradicen bajo concurrencia**: caché direccionada por
+contenido *y* workspace mutable de build (patches, `cargo vendor`, `go mod vendor` — minutos y
+decenas de GB). Por eso **un lock sólo en la extracción parece correcto y NO lo es**: un segundo
+proceso puede borrar un árbol que un `bwrap` está usando para compilar.
+
+Medido a escala: al invalidar `libdrm`, ~93 de 205 recetas KDE murieron con `/src/.zwrap/cc is not a
+full path to an existing compiler tool` — el wrapper de zig que la receta deja EN EL ÁRBOL, barrido
+por el fetch concurrente de otra receta.
+
+Mitigación vigente (no solución): **`flock work/.farm-build.lock` alrededor de todo `hammer build`**,
+tomado también por `farm-worker-loop.sh` y `campana-deuda.sh`, y `JOBS=1` en el worker. Opciones
+sobre la mesa, ninguna elegida: (A) lock por árbol sostenido durante todo el build —correcto pero
+serializa el diamante que converge en qtbase/kcoreaddons—, (B) árbol privado por constructor, (C)
+caché inmutable + copia.
+
+### 14.2 No-determinismo en `.debug_*` (SDD 23)
+
+92 paquetes con no-determinismo probable. La causa medida **no** son rutas del host sino **rutas
+internas al árbol de build que varían entre corridas** (`/src/output/meson-private`). Verificado con
+`hammer why-differs`: `binutils` y `wl-clipboard` reproducen; `appstream` y `bison` **divergen**, y
+la divergencia vive ENTERA en secciones `.debug_*` (el código ejecutable es idéntico).
+
+**La buena noticia estructural:** las dos mitades del plan son el mismo trabajo — **separar el debug
+hace que el artefacto principal reproduzca**, sin tocar `-ffile-prefix-map` en ~720 recetas. Y el
+tamaño: ~60 % del *artefacto* es información de depuración (el «79 %» que se citaba medía el
+*contenido binario*, no el artefacto) ⇒ el espejo público baja de ~126 G a ~51 G.
+
+**Estado de la campaña** *(medido)*: sólo **35 recetas** declaran `strip_debug`, todas en el corpus.
+Es decir: el mecanismo está implementado y el plan escrito, pero el barrido está en su arranque —
+y cada tanda re-hashea lo que toca, así que compite por la misma granja que la reconstrucción del
+corpus (§13.3).
+
+### 14.3 Otros frentes abiertos
+
+- **36 recetas con `compiler = "gcc"`** *(medido: 16 en el corpus + 20 en las colas de escritorio;
+  contadas parseando TOML, porque `grep` cuenta comentarios)*. Las 12 de Rust son **un solo
+  problema**: falta el unwinder de libgcc (perilla `LIBS=-lunwind` ya identificada).
+- **Techo MSRV del sandbox = 1.96.** La frontera son los `*-sys` con C++ — y es **exactamente** lo
+  que bloquea Firefox. *Antes del navegador hay que subir el techo.*
+- **`*-sys` que piden libs C sin `[deps]`**: `unable to find static system library 'lzma'/'bz2'` en
+  recetas Cargo. Arreglo por receta (`xz`, `bzip2`, `sqlite`), **nunca engordar el rootfs**, y
+  **nunca en bloque**: hay ~220 recetas Cargo sin deps y re-hashearlas invalidaría las selladas.
+- **Multi-init**: los inits del catálogo (openrc, etc.) **compiten** con arje-zero, no se apilan.
+- **`link=static` es a veces mentira**: cuatro causas distintas (libtool se come el `-static`; el
+  Makefile lo pisa; `link` es INERTE en Go; la receta Cargo pisa el lab y pierde `crt-static`).
+  Auditado: 0/680 mienten hoy, pero el audit **sobre-reporta si no se re-sella antes**.
+- **Licencias**: 1096/1197 declaran; faltan ~101 y hay que desambiguar SPDX obsoletos (`GPL-3.0` no
+  dice si `-only` u `-or-later`). Regla dura: **no se rellena a ojo** — declarar mal es peor que
+  dejar vacío, porque convierte un hueco visible en una afirmación falsa. El cierre definitivo es
+  capturar la licencia en la **fase de fetch**, donde el dato pasa gratis.
+- **Falta para publicar** (SDD 19/20/21): espejo de fuentes servido, **clave raíz fuera de línea**
+  (lo único no delegable a un agente), imagen validada en **metal** con pantalla, upgrade N→N+1 con
+  rollback probado, `cups` y `bluez`, documentación mínima y canal de reportes.
+- **Aplicaciones gráficas de terceros: cero.** Barato: imv, zathura, mpv, gestor de archivos.
+  Caro: LibreOffice. El más caro del catálogo: un navegador.
+
+### 14.4 Modos de fallo estructurales que este proyecto descubrió (y que valen como principio)
+
+1. **Un artefacto VACÍO es un cache-hit.** `hammer build` miraba presencia del directorio, no
+   contenido ⇒ sellaba sin construir y salía OK. Siete vacíos se propagaron de respaldo →
+   manifiesto → grafo → store → build: **en cada eslabón el vacío se lee como presencia**. Regla:
+   *un ausente falla ruidosamente; un vacío llega hasta el final diciendo que todo fue bien.*
+2. **El cache-hit congela regresiones.** «No re-hashea nada sellado» ≠ «es seguro»: una regla del
+   wrapper llevaba meses rompiendo `make` sin que se viera, porque el artefacto viejo se servía por
+   caché. Al reconstruir: exit 139 en medio corpus. ⇒ **tras tocar el harness, reconstruir a
+   propósito una receta de cada tipo.**
+3. **Con flota efímera y sync unidireccional, un escritorio puede dejar de ser
+   reconstruible-desde-el-store sin que ningún indicador lo diga.** Pasó con KDE: los workers donde
+   vivían esos artefactos ya no existen, el sustrato cambió, las sombras se re-hashearon y nadie las
+   reconstruyó. El grafo seguía diciendo 162/162 en un JSON generado semanas antes.
+4. **`grep` no es una medición en este repo.** Contó comentarios en el conteo de licencias
+   (`grep -l license` → 5 falsos positivos), contó `IO_URING` en un comentario del kernel, y contó
+   `.rela.debug_*` en la regla del `.a` no-PIC (`readelf -r | grep R_X86_64_32` **miente** si no se
+   excluye debug). El veredicto se obtiene **reconstruyendo y comparando**, no buscando cadenas.
+5. **`.dmerge` retiene lo que borrás del store** (hardlinkea los artefactos) ⇒ podar el store no
+   libera disco mientras la caché los referencie. Medir con `st_nlink==1`, **nunca con `du`** (suma
+   los hardlinks otra vez).
+
+---
+
+## 15. La frontera (SDD 15, 17, 18) — lo diseñado pero no cerrado
+
+### 15.1 SDD 15 — proof-carrying, cómputo como dato, código por contenido
+
+- **H1 — Proof-carrying recipes ✅**: el `.swm` lleva un bloque `evidence` (`{kind, cmd, expected}`,
+  `kind ∈ {cmd-exit, proptest, contract, kani}`); `hammer swm-verify --evidence` corre cada ítem
+  **dentro del sandbox reproducible**. La evidencia corre del lado del BUILD (separación
+  PROPONE/CONSTRUYE: `hammer-agent` no depende de `hammer-build`); `Event::BuildReady` lleva el
+  `EvidenceVerdict` y el `Orchestrator` **aborta antes de hidratar** si falla. **Frontera honesta
+  declarada**: garantiza que *lo declarado pasa*, no que *lo declarado basta*.
+- **H2 — Memoización de ejecución ✅ (en `wawa-memo`, repo hermano)**: si el wasm es determinista,
+  `blake3(módulo) + blake3(entrada) + E → blake3(salida)` es una función pura y su caché es un
+  `LwwMap` monótono compartible por anti-entropy. `E` es un hash-de-entorno que pinea
+  wasmi/features/fuel/mem/ABI. Único vector residual acotado: payload de NaN vía `reinterpret`.
+- **H3 — Código por contenido estilo Unison ✅ (subconjunto)**: `Base` content-addressed de
+  funciones wasm — identidad = `blake3(bytecode)`, nombres como metadata separada, **actualizar no
+  rompe** (v1 y v2 coexisten), composición por hash. **ADR 0009 decide NO reescribir la unidad de
+  compilación**; el puente barato es procedencia por-símbolo como metadata.
+- **H4 — Configuraciones compartidas ✅**: el eje nuevo son los **slots**. Una modificación
+  **reclama** (`claim: slot→Id`) y **requiere** (`requires: slot→Id`). *Compatible* queda definido
+  duro: requisitos resueltos + reclamos disjuntos. Dos casos que el modelo trata distinto: **logo**
+  (dos configs escriben la misma superficie ⇒ **colisión = elección**) y **wayland** (dependencia
+  que no resuelve ⇒ **rechazo**). Subido a la receta real (`[slots]`, fuera de `hash_inputs`) y a la
+  `InstalledDb` (`system_state() → slot → hash`). CLI: `hammer compat`.
+
+**La ambición declarada al final del SDD 15**: `config = paquete = función = proceso`, todos el
+mismo tipo de objeto direccionado por el mismo hash.
+
+### 15.2 SDD 17 — los diez cierres de frontera, priorizados
+
+**Tesis:** los tres invariantes ya pagados —**bit-reproducibilidad**, **direccionamiento por
+contenido** y **clausura-como-política**— hacen casi gratis para hammer lo que a otros les cuesta.
+La búsqueda correcta no es «qué frontera agregar» sino **«qué cae solo del invariante»**.
+
+1. **Consenso de reconstrucción** — N builders independientes reproducen el mismo hash ⇒ el binario
+   es confiable **sin que nadie firme nada**. Convierte la repro de QA en primitiva de distribución:
+   cualquiera puede ser mirror, nadie puede envenenar el repo. Nix/Debian no pueden ofrecerlo.
+2. **`why-differs`** (diffoscope propio) — **implementado**: nombra la causa (un MTIME en un gzip,
+   la cabecera `ar` de un `.a`, qué sección ELF cambió, una ruta filtrada). Multiplicador de todo lo
+   demás.
+3. **Política de runtime derivada del closure** — fase 3 de harkaq. Con **dos correcciones medidas**:
+   (a) la clausura de build **no puede** ser la política de runtime porque le **sobra** casi todo
+   (un `htop` musl-estático bajo la jaula **no tocó un solo fichero** fuera de sí mismo; derivarla
+   del build le habría concedido headers y compilador, **firmados**) ⇒ la política granular **se
+   MIDE corriendo el binario**; (b) la **lección casper**: hay una clase —*servicios del mundo*—
+   que no son paths y no caben en ninguna jaula por-fichero, y el build offline no la contiene ⇒
+   **frontera** (clases `dns`/`tls-certs`/`random`/`locale-tz`/`syslog`, **bits de un `u32`
+   firmado** de 36 bytes canónicos) + **detalle** (paths, Landlock, medidos).
+4. **CVE por grafo** — `hammer affected CVE-X` exacto + frontera mínima de rebuild + hydrate para
+   repartir el parche rebuild-free. Donde Debian/Alpine aproximan por nombre-versión, hammer
+   responde exacto.
+5. **Sellar el arranque** — verity + medida; el store CAS hace fs-verity casi gratis.
+6. **Matar el trusting-trust: DDC** — con la cadena mrustc cerrada, hammer es de los poquísimos que
+   puede **ejecutar** Diverse Double-Compiling. Un fin de semana de granja, resultado publicable.
+7. **De-Alpinizar el rootfs del sandbox** — canal impuro **estructural** mientras el sandbox se
+   apoye en Alpine; harkaq lo mide.
+8. **`hammer oci`** — imágenes distroless bit-reproducibles desde un closure. Vector de adopción
+   realista.
+9. **Updates diferenciales content-defined** (chunking estilo casync sobre el store).
+10. **Bucle agéntico con juez mecánico** — LA tesis AI-nativa: *el catálogo se cultiva solo porque
+    el juez es mecánico*. Sin juez, las recetas de un LLM son deuda; con él, son cosecha. Ya
+    demostrado a mano con `zlib` (Impuro→Hermetico en 3 fases, guiado sólo por harkaq).
+
+**Qué NO priorizar** (dicho explícitamente): solver SAT de versiones (el grafo es curado), multi-arch
+(esperar demanda), config declarativa estilo NixOS (cancha ajena).
+
+### 15.3 SDD 18 — wawafs: el store como filesystem del sistema vivo
+
+**Diseño aceptado, sin implementar.** Bajar el checkout de userspace al kernel con la pila
+**composefs** (CAS + erofs + overlayfs + fs-verity, todo mainline): no hay que escribir un
+filesystem. Capas: proceso → VFS (rutas FHS reales, sin pueblo fantasma) → composefs mount →
+manifest erofs por clausura (KB–MB, cero datos) → CAS de objetos → fs-verity opcional → ext4.
+
+Propiedades que caen solas: dedup de disco **y de page cache**, activar/rollback **O(metadata)**,
+inmutabilidad estructural, y **bit-repro en runtime** (el kernel verifica el Merkle en cada lectura;
+un bit podrido = EIO, no un binario silenciosamente distinto).
+
+**Identidad: dos granos, un mapa.** blake3 sigue siendo la identidad (recetas, sellado, grafo); el
+digest fs-verity (sha256) es el *enforcement*. Misma jugada que ADR 0009.
+
+ADR 0005 (hardlinks) **sigue vigente** para el laptop de desarrollo; wawafs es el modo **despliegue**
+(ro). El corte cae donde el workload cambia de naturaleza: se escribe una vez al sellar, se lee mil
+veces al vivir. Prototipo medido (2026-07-17): `hydrate` 28 ms vs `mkcomposefs` 507 ms sobre un
+rootfs de 262 MB; manifiesto `.cfs` de **136 KB firmables**. Hallazgo: **con hardlinks, un write con
+privilegios a través del árbol hidratado corrompe el store** (comparten inode); con composefs es
+imposible por construcción. Precondición: el kernel propio trae OVERLAY_FS+REDIRECT_DIR+METACOPY
+pero **falta `EROFS_FS` y `FS_VERITY`** ⇒ re-sellado de kernel.
+
+---
+
+## 16. Los ADR, con su estado
+
+| # | Decisión | Estado |
+|---|---|---|
+| 0001 | Rust para el tooling y los daemons | aceptada |
+| 0002 | Validar sobre Alpine antes de la distro propia | aceptada |
+| 0003 | `zig cc` como compilador por defecto del lab | aceptada |
+| 0004 | **No escribir nuestro propio Nix** | aceptada |
+| 0005 | Hidratación por hardlinks | aceptada (convive con wawafs como backend futuro) |
+| 0006 | Commits fijados, no `HEAD` vivo | aceptada |
+| 0007 | `arje` como init propio del track posterior | propuesta (implementada de facto) |
+| 0008 | Bootstrap en 3 stages, `zig` como semilla | aceptada |
+| 0009 | Código direccionado por contenido (Unison): registrar la visión, no reescribir | propuesta / design-doc |
+| 0010 | Arranque por grafo: el menú de boot como navegación del grafo | aceptado; lado hammer 1–5 ✅ |
+| 0011 | Etapa Escritorio: campaña KDE Plasma 6 | aceptado; gates H-Qt y H-QML pasados |
+| 0012 | El árbol de fuentes es caché y workspace a la vez | **PENDIENTE, sin decidir** |
+| 0013 | Mirror de fuentes: la URL es transporte, el `sha256` es la identidad | ACEPTADO, implementado |
+
+---
+
+## 17. Superficie del CLI (`hammer`)
+
+```
+build · hash [--check] · why-differs · attest · hydrate
+try · commit · discard · status · journal
+apply · swm-verify [--evidence] · swm-sign · keygen · export
+pack · install · uninstall · installed · compat · repo {list,sign,verify}
+import-nix · import-alpine · pin
+ai [--catalog|--llm] [--repair-max-attempts] · ctl · query
+bootstrap {stage0,stage1,stage2,builder,all,manifest}
+mirror {push,pull,status}
+upgrade {apply,rollback,recover,status,prune}
+boot {graph,activate,menu}
+kernel {stats,probe,bundles,closure,plan,diff-back,gate,hw}
+```
+
+Daemon: `hammerd [--journal DIR] [--agent-caps FILE]`.
+
+---
+
+## 18. Reglas del repo (CLAUDE.md) — no son estilo, son seguridad
+
+1. **Todo `hammer build` va envuelto en `flock work/.farm-build.lock`.** Por el ADR 0012. Para una
+   tanda, tomar el lock **una sola vez**, no por receta. El lock está **fuera** de `hammer build` a
+   propósito: los scripts de granja lo toman por fuera y hammer se bloquearía contra ellos.
+2. **Nunca `git add -A`.** Varios agentes trabajan el repo a la vez; un `add -A` arrastra ficheros a
+   medias de otro. Commits granulares, en español, directo sobre `main`, y `git push` tras cada
+   unidad de trabajo.
+3. **Antes de dar un artefacto por presente, mirá que tenga contenido.** (Ver §14.4.1.)
+
+---
+
+## 19. Vocabulario del proyecto (para leer commits y docs sin perderse)
+
+| Término | Qué es |
+|---|---|
+| **el lab / el sótano** | El laboratorio de build hermético |
+| **sellar** | Depositar un artefacto en el store bajo su hash |
+| **hidratar** | Proyectar un artefacto del store al FHS |
+| **la granja** | La flota efímera de workers hcloud |
+| **el hub** | La máquina con secretos (laptop o gioser); firma, gitea, clasifica |
+| **el latido** | El cron */30 que cosecha, siembra y regenera el grafo |
+| **cola / queue** | Un árbol de recetas: `corpus` = `recipes/`; el resto `recipes/incoming-<x>/` |
+| **sombra** | Una receta homónima en otra cola que gana por sibling-first |
+| **deuda** | Nodo del grafo sin artefacto en su hash **vigente** |
+| **drenar** | Construir la deuda en ondas topológicas |
+| **frontera** | Lo que upstream pide y no tenemos |
+| **keystone** | Nodo en deuda que gatea el máximo trabajo bloqueado |
+| **radio** | Cuántos nodos rompo si toco una receta |
+| **superado / huérfano** | Artefacto con ejemplar mejor (podable gratis) / ejemplar único |
+| **de-Alpinizar** | Quitarle a una receta la dependencia implícita del rootfs Alpine |
+| **harkaq** | La jaula (Landlock+seccomp) que verifica que lo declarado basta |
+| **yupana / khipu** | El motor de cálculo sobre el estado / el registro firme del estado |
+| **arje / arje-zero** | El init propio (PID 1) del monorepo hermano `tawasuyu` |
+| **mirada** | El compositor Wayland de tawasuyu que dibuja el menú de arranque |
+| **wawa / wawafs** | El runtime wasm de tawasuyu / el frente de store-como-FS |
+
+---
+
+## 20. Dónde hay hueco para SDDs nuevos
+
+Esto es lectura del autor de este informe, no doctrina del repo. Los huecos donde un SDD tendría
+tracción real, con la razón por la que hoy no existe uno:
+
+1. **ADR 0012 resuelto → SDD del modelo de árbol de fuentes.** Es la única decisión estructural
+   marcada PENDIENTE, bloquea el paralelismo del worker (`JOBS=1`) y ya rompió una campaña entera.
+   Cualquier propuesta debe cubrir **extracción + build**, no sólo la extracción, y decir qué pasa
+   con `cargo vendor` (minutos, decenas de GB) y con el diamante que converge en qtbase.
+2. **SDD del split de debug terminado** (SDD 23 está a medias): qué se hace con el paquete `-debug`
+   en sí, cómo viaja por el repo `.swm`, y si `-ffile-prefix-map` entra o no. Decide ~96 G de disco
+   y el tamaño del espejo público.
+3. **SDD de gestión de claves y cadena de release.** Hoy hay firma de índice; falta clave raíz
+   offline, rotación, procedimiento de compromiso. Es **bloqueante para publicar** y el propio SDD
+   21 lo marca como lo único no delegable a un agente.
+4. **SDD de política de runtime** (cierre §3 de SDD 17): el diseño de dos niveles ya está medido
+   —frontera como bits de un `u32` firmado, detalle medido corriendo el binario— pero no hay
+   documento que lo fije ni herramienta que lo emita end-to-end.
+5. **SDD de consenso de reconstrucción** (cierre §1 de SDD 17): el más diferenciador y el que
+   convierte la reproducibilidad de QA en primitiva de distribución. Las piezas (granja,
+   `bootstrap.json`, `fork-proof`, `umbral`) existen; falta el protocolo.
+6. **SDD de validación de imagen: de «cierra en el grafo» a «arranca».** La lección
+   *sellado ≠ arranca* costó seis muros y ocho ciclos y **no tiene documento**: hoy vive como
+   anécdota en el §II.1 del SDD 20. Un harness reproducible (contar colores del PPM, comparar
+   `NEEDED` del ELF contra el rootfs, verificar datos XKB/tipografías/`/run` rw) evitaría pagarlo
+   una vez por perfil.
+7. **SDD del catálogo de aplicaciones gráficas**, con el techo MSRV como precondición explícita: hoy
+   el navegador está presupuestado como frente propio pero sin plan escrito, y el orden correcto
+   (subir el techo → `*-sys` con C++ → Firefox) sólo aparece como nota en el SDD 21.
+8. **SDD de wawafs F0–F3** ya existe (SDD 18) pero está sin implementar y sin gate ejecutado; su F0
+   es barato (spike en QEMU con composefs de paquete) y decide si el frente existe.
+9. **SDD de la fase de fetch como capturadora de metadatos** (licencia, y por extensión SBOM): el
+   propio SDD 20 identifica que ahí el dato «pasa gratis», y cerraría la deuda de licencias para
+   siempre en vez de por barrido.
+
+---
+
+## 21. Cómo leer el repo (mapa de ficheros)
+
+```
+docs/00..23-*.md        los SDD (00 visión … 23 re-hasheo)
+docs/adr/               las 13 decisiones
+docs/runbooks/          operativos: stage1-vm-boot, self-hosting-toolchain, kde/gnome/cosmic-desktop,
+                        armador-de-kernel, harkaq-*
+docs/state/             build-state*.json (el khipu), targets.toml (el objetivo), drenaje.json,
+                        lab-toolchain.lock, kernel-bundles.toml, keystones.json, fuentes-vigia.json
+docs/evidencia/         capturas PNG que sostienen las afirmaciones de arranque
+crates/                 los 12 crates del workspace
+recipes/                787 recetas del corpus + colas incoming-{kde,gnome,cosmic,wlr,…}
+scripts/                yupana.py (puerta única), build-state.py, drenar.py, targets.py,
+                        why-differs.py, store-gc.sh, licencias*.sh, verificar-repro.sh,
+                        selfhost-verify.sh, reconstruir-corpus.sh, lab-image.sh,
+                        farm/ (30 scripts de granja), harkaq/ (19), fuentes/ (6), rust-frontier/ (8)
+store/                  el CAS (bind-mount al volumen dedicado)
+work/                   sources/, repos/, locks, logs de campaña
+```
+
+**Punto de entrada recomendado para un agente nuevo:** `CLAUDE.md` → `docs/00-vision.md` →
+`docs/01-architecture.md` → `docs/10-roadmap.md` → `docs/state/build-state.json` (regenerándolo) →
+`scripts/yupana.py radio <receta>` antes de tocar nada compartido.