# 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/-/ (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/-/ 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:" XOR "tarball:" ⧺ compiler ⧺ target ⧺ link ⧺ lab: // ← desde 2026-08-10 ⧺ [zig:] si está fijado ⧺ [strip_debug:] 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 ` 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 ` 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__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 --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 ` 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 --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 ` 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 ` (¿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/-`. 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-/` | | **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 ` antes de tocar nada compartido.