gioser

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 1346Shell 97Rust 63Markdown 571650 Sutichaykuna

⎇ 0818392847a3b93a34e20ab2868e69f7c95bdcc8 docs/BRIEFING-takana.md Markdown · 915 siq'ikuna · 54.2 KB Ñawpa willay ch'uya

renombre: los instaladores pasan a `takana-*` — y el peligro no era el fichero, era el PROTOCOLO 008dd3925 · Sergio ·

1# takana — informe de arquitectura completo (briefing para un agente externo)
2
3> **Para qué es este documento.** Dar a un modelo que NO tiene acceso al repositorio el contexto
4> suficiente para razonar sobre takana al nivel de proponer SDDs nuevos: arquitectura, capas,
5> componentes, invariantes, decisiones tomadas, estado real medido, deuda abierta y las lecciones
6> de método que este proyecto pagó caro.
7>
8> **Fecha de corte:** 2026-08-28. Todo número marcado *(medido)* salió de un comando corrido ese
9> día contra el repo; los demás llevan la fecha del documento del que vienen.
10>
11> **Idioma y convención:** el repo entero está en español, incluidos commits, docs y comentarios.
12> Los SDD son documentos de diseño numerados (`docs/NN-*.md`); los ADR son decisiones
13> (`docs/adr/NNNN-*.md`). Cuando el código y el SDD discrepan, o se corrige el código o se
14> actualiza el SDD con un commit que explica por qué.
15
16---
17
18## 0. Resumen ejecutivo en diez líneas
19
20`takana` es una **distribución Linux + su gestor de construcción**, escrita en Rust, cuya tesis es
21separar dos mundos hoy fusionados:
22
23- **El sótano (el laboratorio):** compilación determinista, hermética y direccionada por contenido
24  (BLAKE3). `bubblewrap` + `zig cc` + musl. Cada artefacto es un árbol de ficheros sellado bajo su
25  hash de **entrada**.
26- **El piso de arriba (el userland):** un Linux clásico, mutable, con FHS real (`/bin`, `/lib`,
27  `/etc`). Los binarios se **hidratan** desde el store por hardlinks.
28
29Entre ambos viven tres piezas propias: **overlay** de experimentación (`try`/`commit`/`discard`),
30**diario de mutaciones** por `fanotify` (la configuración se deriva *a posteriori*, no se declara
31antes), y el **`.swm`**, un manifiesto de mutación que viaja en vez del binario — el receptor
32**reproduce y verifica**. Encima corre un **bus de agente** (`/run/agent.sock`) por el que una IA
33propone, construye, prueba en overlay y presenta; el humano hace `commit`.
34
35El proyecto **ya cerró** el bucle AI-nativo completo, el bootstrap auto-alojado bit-reproducible
36(incluido el toolchain y el kernel), cuatro escritorios construidos desde fuente, formato de
37paquete/repositorio/instalador, imágenes ISO/USB/EFI e infraestructura de granja de compilación.
38Lo que falta para publicar es sobre todo legal, de claves y de validación en metal.
39
40---
41
42## 1. Filosofía y anti-objetivos (SDD 00)
43
44**El problema.** Las distros inmutables/declarativas (NixOS, Guix, Silverblue) dan determinismo a
45costa de agencia: el SO *es* el grafo, se pierde la estructura familiar y la IA tiene que simular a
46un humano tras capas de abstracción. Las tradicionales (Arch, Gentoo) dan control pero se pudren:
47el gestor pierde el rastro de lo que el usuario hizo a mano.
48
49**La tesis.** *«La automatización funcional es mi empleada en el sótano. En el piso de arriba mando
50yo.»* Determinismo en la fábrica, libertad en la ejecución; ninguno de los dos se negocia.
51
52**Por qué es AI-nativo** (el diferenciador real, no la distro en sí):
531. Compilación determinista ⇒ la IA cambia fuente, recompila, y el binario es predecible.
542. El sistema ejecutable es **texto plano + binarios atómicos** ⇒ nada de bases de datos binarias
55   opacas (registro, journal binario). Se lee con `cat`, `grep`, `readelf`.
563. Mutabilidad = agencia real, con overlay como red de seguridad.
574. **musl** mantiene el C legible para el contexto de un LLM (glibc es un laberinto de macros).
585. El enlazado estático evita que mutar una herramienta rompa las librerías de otra.
59
60**Principios.** Verificar, no confiar · texto plano por encima de bases opacas · la IA propone, el
61humano dispone · no reinventar lo resuelto · **el diario es la verdad** (se vive imperativamente y
62el estado declarativo se deriva después, nunca al revés).
63
64**Anti-objetivos.** No es un gestor inmutable; no reescribe un motor de build (ADR 0004: **takana
65NO usa nix**); no compila contra `HEAD` vivo (ADR 0006); no oculta complejidad tras abstracciones.
66
67---
68
69## 2. Arquitectura general (SDD 01)
70
71### 2.1 Modelo de dos mundos
72
73```
74   EL SÓTANO (laboratorio)                    EL PISO DE ARRIBA (userland)
75   determinista · hermético                   mutable · clásico · FHS real
76
77   upstreams (commits/tarballs FIJADOS)
78        │
79   takana-build (bwrap + zig cc)   ──sella──▶  CAS store  ──hidrata──▶  /bin /lib /etc
80   recetas + grafo de deps                    /store/<b3>-<name>/       (hardlinks)
81                                                   │                        │
82                                                   ▼                        ▼
83                                            overlay try/commit      diario fanotify
84                                                   │                        │
85                                     BUS DE AGENTE /run/agent.sock (JSON-líneas)
86                                     COMPILE · INJECT · QUERY · eventos
87                                                   ▼
88                                            [ IA programadora ]
89```
90
91**Regla de oro:** los dos mundos nunca se mezclan. El lab jamás escribe en el FHS real (pasa por
92store + hidratación); el userland jamás compila contra el host (delega al lab).
93
94### 2.2 Los crates (workspace Rust, edición 2021, `opt-level="z" + lto + panic=abort`)
95
96| Crate | Responsabilidad única |
97|---|---|
98| `takana-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) |
99| `takana-build` | El laboratorio: `sandbox` (bwrap), `fetch`, `download`, `hydrate`, `swm_bridge`, `harkaq` (la jaula), `config` |
100| `takana-bootstrap` | Orquesta stage0/stage1/stage2/builder/all + `manifest` (log de transparencia) |
101| `takana-overlay` | `try`/`commit`/`discard`/`status` sobre overlayfs, con apilamiento LIFO |
102| `takana-journal` | Diario append-only JSON-líneas, `content_hash`, de-dup idempotente |
103| `takana-mirror` | Replicación del store por hash entre máquinas (verifica `of_tree` al recibir) |
104| `takana-upgrade` | Generaciones, apply/rollback/prune/recover, `boot_graph` (ADR 0010) |
105| `hammer-recover` | Mini-binario estático que auto-sana un upgrade interrumpido, al arranque |
106| `takana-agent` | `AgentClient`, `IntentTranslator` (+ `MockTranslator`, `ClaudeTranslator`), `Orchestrator` |
107| `takana-cli` | El binario `takana` (+ `alpine_import`, `nix_import`, `kernel_cmd`) |
108| `hammerd` | Daemon: bus de agente, watcher fanotify, FIFO de init, `crashes`, `arje_link` |
109| `netup` | DHCP/netlink mínimo para el producto |
110
111### 2.3 Estado en disco
112
113```
114/store/<blake3>-<name>/     artefacto sellado, inmutable, append-only
115/var/lib/hammer/
116  recipes/  pins.toml  journal/  overlays/  trust/  installed.json  upgrades/
117/run/init.control           FIFO de control humano (texto crudo)
118/run/agent.sock             socket de agente (JSON-líneas, SO_PEERCRED)
119/run/hammer/boot-graph.json grafo de estados que dibuja el menú de arranque (ADR 0010)
120```
121
122---
123
124## 3. Definiciones canónicas (lo que hay que entender para proponer cambios)
125
126### 3.1 `Recipe` — el átomo del grafo
127
128TOML declarativo y puro. Campos reales (`takana-core/src/recipe.rs`):
129
130```toml
131name = "grep"
132version = "3.12"
133license = "GPL-3.0-or-later"     # FUERA de hash_inputs (ver §3.2)
134
135[source]
136repo = "…" ; commit = "…"        # git  … XOR …
137tarball = "…" ; sha256 = "…"     # tarball
138strip_components = 1
139patches = ["…patch"]
140cargo_vendor = true              # perilla del vendoreo Cargo
141cargo_vendor_dir = "…"           # cuando el proyecto ya trae su vendor/
142
143[build]
144compiler = "zig-cc"              # "zig-cc" | "clang" | "gcc"
145target   = "x86_64-linux-musl"
146link     = "static"              # "static" | "dynamic"
147flags    = [...]
148zig_version = "0.13.0"           # pin opcional (84 recetas lo fijan)
149strip_debug = true               # separa .debug_* (SDD 23) — SÍ entra al hash
150cgo = false
151subdir = "…"
152[build.phases] configure/compile/install  # overrides opcionales
153
154[deps] build = [...] ; runtime = [...]
155[evidence] checks = [...]        # H1 proof-carrying (fuera del hash)
156[slots] claims = {...} ; requires = {...}   # H4 compatibilidad (fuera del hash)
157```
158
159### 3.2 `ArtifactHash` — hash de **entrada**, no de salida
160
161```
162artifact_hash = BLAKE3(
163      source_id            // "git:<commit>" XOR "tarball:<sha256>"
164    ⧺ compiler ⧺ target ⧺ link
165    ⧺ lab:<LabFingerprint>          // ← desde 2026-08-10
166    ⧺ [zig:<version>]  si está fijado
167    ⧺ [strip_debug:<bool>] si está fijado
168    ⧺ contenido de cada patch
169    ⧺ cada flag
170    ⧺ phase:configure/compile/install si hay override
171    ⧺ Σ dep.artifact_hash )
172```
173
174Consecuencias que gobiernan casi todas las decisiones del proyecto:
175
176- **`hash_inputs` es una LISTA BLANCA.** Lo que no está enumerado no mueve el hash. Por eso
177  `license`, `evidence` y `slots` se pudieron añadir **sobre recetas ya selladas** sin re-hashear
178  nada. Es el truco recurrente para pagar deuda barata.
179- **Las fases de build SÍ entran.** ⇒ editar el `-j` de una fase cambia el hash aunque el binario
180  sea idéntico; ⇒ el `.config` del kernel (que vive en la fase `configure`) **es la identidad del
181  artefacto** (SDD 22 §1); ⇒ `strip_debug` obliga a re-hashear el corpus (SDD 23).
182- **El hash es de entrada, no de contenido.** `takana hash <receta>` responde en ~2 ms cuál es el
183  hash VIGENTE sin construir; `--check` dice si ya está sellado. `ArtifactHash::of_tree` es la otra
184  cosa: el hash del **contenido** real de un árbol, usado para verificar reproducibilidad y para el
185  mirror.
186- **Corolario peligroso:** `strip` posterior al sellado rompería la verificación bit-a-bit, porque
187  el hash de entrada seguiría apuntando a algo que un rebuild ya no reproduce.
188
189### 3.3 `LabFingerprint` — el toolchain como entrada (2026-08-10)
190
191Se computa del **apk db del rootfs real** que va a compilar (no de un fichero declarado). Entran
192compiladores/enlazadores (gcc, clang, llvm, binutils, rust, cargo), las libs de codegen de gcc
193(gmp, mpfr, mpc, isl), el runtime que se enlaza dentro (musl, libgcc, libstdc++, libatomic,
194libgomp) y los headers que se compilan dentro (linux-headers, fortify-headers). Quedan fuera **a
195propósito** autotools y shells: es una decisión de coste, no una afirmación de que no influyen.
196
197**Por qué existe:** reconstruyendo los cuatro kernels en un segundo hub, el `.config` instalado salió
198distinto en 4 líneas (`CONFIG_RUSTC_VERSION 109600→109700`) — Rust 1.96 vs 1.97 del rootfs, y **Rust
199ni siquiera estaba activado en ese kernel**. Antes de esto, dos labs sellaban bytes distintos en la
200MISMA dirección y el store no podía notarlo.
201
202### 3.4 Store, hidratación, overlay, diario
203
204- **Store**: inmutable, append-only, deduplicado, GC por alcanzabilidad (`scripts/store-gc.sh`
205  distingue SUPERADO —hay ejemplar mejor— de HUÉRFANO —ejemplar único—). `Store::has` rechaza
206  directorios vacíos (un vacío es un nombre, no un artefacto).
207- **Hidratación** (ADR 0005): hardlink directo en el caso estático; en el dinámico se **copia** y
208  se reescribe con `patchelf --set-interpreter/--set-rpath` (no se hardlinkea, o patchelf mutaría
209  el inode del store). Escribir encima rompe el hardlink ⇒ el store queda intacto como base de
210  rollback.
211- **Overlay** (SDD 04): overlayfs con `upperdir` mutable sobre el FHS real. `try`/`commit`/
212  `discard`/`status`. El apilamiento es total y determinista (`created_at` + `id` + `seq`) y
213  `commit`/`discard` exigen **LIFO** (`Error::Shadowed` si una capa más joven sombrea el target).
214- **Diario** (SDD 05): `fanotify` (FAN_CLOSE_WRITE) sobre `/bin`,`/sbin`,`/usr/bin`,`/usr/sbin`,
215  `/lib`,`/usr/lib`,`/etc`; JSON-líneas append-only; `op ∈ {Create, Edit, Delete}` derivado por
216  `classify_op`; `content_hash` tras la mutación con de-dup idempotente. Distingue mutación
217  **trazable** (vino de un artefacto conocido) de **opaca** (pisada a mano) sin prohibir ninguna.
218
219### 3.5 `.swm` — la unidad de intercambio (SDD 06)
220
221YAML con `base` (distro_version + pins), `mutations` y `signature` opcional (Ed25519). Cuatro tipos
222de mutación: `source_patch` (una `Recipe` serializada para viajar), `config_edit` (hunks exactos,
223sin fuzz), `init_rule` y `file_drop` (`content_b64` XOR `content_url`, siempre con `content_hash`
224BLAKE3 verificado **antes** de escribir nada).
225
226`takana apply` verifica base → verifica firma → **reproduce** en el lab local → aísla en overlay →
227el humano valida → `commit`. **Nunca se ejecuta un binario ajeno.**
228
229### 3.6 Repositorio de paquetes (Etapa F)
230
231Un repo son ficheros estáticos: `index.json` + los `.swm`. Un `.swm` no tiene identidad propia; la
232identidad la asigna el índice al publicar (**el índice es el namespace**). `install <nombre>`
233resuelve el **cierre transitivo** de build-deps del índice (topológico), sintetiza un catálogo de
234recetas efímero desde los `.swm` de las deps y reproduce desde fuente. El índice entero se firma
235como **release** (cualquier `pack --repo` la invalida ⇒ re-firmar). Consumible por HTTP(S).
236
237---
238
239## 4. El laboratorio de build (SDD 02)
240
241### 4.1 El sandbox
242
243`bubblewrap` con `--unshare-all` (net, pid, mount, ipc, uts, user). Base y deps montadas
244read-only con `--overlay-src` + `--tmp-overlay /`; las únicas superficies RW son `/src` (árbol de
245fuentes, bind) y `/out` (`DESTDIR`). Entorno mínimo y fijado: `SOURCE_DATE_EPOCH`, `CC`,
246`-mcpu=baseline`, `CARGO_BUILD_JOBS=1`, `CARGO_PROFILE_RELEASE_CODEGEN_UNITS=1`.
247
248**Sin red dentro del sandbox.** El `fetch` ocurre fuera. Corolario que aparece una y otra vez: el
249build es *offline por construcción*, así que su clausura **no contiene** las clases de servicio del
250mundo (DNS, TLS, syslog…).
251
252### 4.2 Fases
253
254| Fase | ¿Sandbox? | Qué hace |
255|---|---|---|
256| `fetch` | No (red) | Clona al commit / baja el tarball y verifica sha256; vendorea Cargo/Go |
257| `patch` | Sí | Aplica `source.patches` |
258| `configure` | Sí | Heurística de build system: autotools / cmake / meson / cargo / make plano |
259| `compile` | Sí | Compila al `target`/`link` de la receta |
260| `install` | Sí | `make install DESTDIR=/out` o equivalente |
261| `seal` | No | Hashea `/out` y lo deposita en el store |
262
263**Materialización de build-deps:** cada `deps.build` se construye recursivamente y su artefacto se
264apila como capa `--overlay-src` BAJO el rootfs del sandbox, dejando sus `usr/{include,lib,
265lib/pkgconfig}` en `/usr`. Así pkgconf y zig cc los encuentran sin plumbing de flags, y las recetas
266sin deps quedan byte-iguales (baseline intacto).
267
268### 4.3 Poliglotismo
269
270- **C/C++**: `zig cc` por defecto (ADR 0003: un binario = compilador + musl + headers, hermético,
271  cross). Escotilla `compiler = "gcc"` para gcc-ismos (hoy ~16 recetas).
272- **Cargo**: triple traducido (`x86_64-linux-musl` → `x86_64-unknown-linux-musl`), wrapper `zig cc`
273  como `CARGO_TARGET_<triple>_LINKER` (cargo no acepta linker de dos palabras), `--release --locked
274  --offline` con `vendor/` obligatorio. `link = "dynamic"` añade `-C target-feature=-crt-static`
275  (necesario para `dlopen` de Vulkan/Wayland).
276- **Go**: 362 recetas *(medido)*; el `go mod vendor` ocurre en el host, antes del sandbox.
277
278### 4.4 harkaq — la jaula (SDD 16)
279
280**Tesis:** bwrap da hermeticidad *respecto del host*, pero monta un **rootfs Alpine entero** como
281capa base ⇒ `política ⊋ clausura`: una receta puede usar cualquier header o binario de Alpine sin
282declararlo y el build pasa en verde. harkaq cierra esa brecha: **la clausura de deps declaradas ES
283la política** (Landlock + seccomp), y el producto es **evidencia negativa**: `hermético ⟺ política =
284clausura ∧ denegaciones = ∅`.
285
286Decisiones cerradas: **D1** política derivada, no escrita · **D2** evidencia negativa (por eso es
287barata) · **D3** cero quieting (las denegaciones SON el producto; ABI 10 trae `ADD_RULE_QUIET` y se
288decide deliberadamente no usarlo) · **D4** seccomp obligatorio, no opcional · **D7** fallo cerrado y
289honesto sobre el ABI (kernel sin Landlock ⇒ build marcado *sin evidencia*, nunca fingir) · **D9** el
290**canario deliberado**: `denials=[]` no se cree, se gana (se antepone un acceso que DEBE ser
291denegado; si no aparece, el lector está roto).
292
293Estado: Fase 0 y Fase 1 cerradas (`zlib` real: `configure→Hermetico`, `compile→Impuro` por
294`/usr/bin/make` no declarado); wiring en `takana-build/src/harkaq.rs` detrás de `HARKAQ=1` e
295**inerte** sin él (requisito duro: 700+ artefactos sellados no pueden cambiar de hash por encender
296un diagnóstico). Barrido en granja: **0 irreducibles** ⇒ la meta de <5% de deuda se cumple.
297Sutileza medida: lo que DAC ya bloquea es invisible a Landlock (el hook no llega a correr) — inocuo
298para el veredicto, trampa para escribir tests.
299
300**Regla operativa aprendida:** el worker MIDE, el hub CLASIFICA (un store parcial infla la métrica).
301
302---
303
304## 5. El bucle agéntico (SDD 07, 08)
305
306### 5.1 Dos canales
307
308- `/run/init.control` — FIFO de control **humano**, texto crudo (`echo "restart network" > …`).
309- `/run/agent.sock` — socket de **agente**: JSON-líneas, handshake `Hello/Welcome`, auth por
310  `SO_PEERCRED`, capacidades leídas de `/etc/hammer/agent-caps.toml` (`default` + `[[rule]]` por
311  uid/gid, primera que casa gana).
312
313Comandos: `Compile`, `Inject`, `Query`, `Init`. Eventos: `BuildReady`, `BuildFailed` (con
314`log_tail` real de 50 líneas del compilador), `Injected`, `QueryResult`, `InitAck`, `Modified`,
315`Crashed`, `Error`.
316
317**Capacidades mínimas:** por defecto un agente sólo obtiene `query`. `compile`, `inject` (a overlay)
318e **`inject-real`** (al FHS) se conceden por separado; `inject-real` exige confirmación humana por
319política.
320
321### 5.2 El bucle
322
323```
324intención NL → PLAN (.swm) → BUILD (lab) → TRY (overlay) → VERIFY (tests + evidence)
325             → PROPOSE (diff + evidencia) → HUMANO decide commit/discard
326```
327
328`takana ai <intent> --catalog F | --llm [--repair-max-attempts N]`. El traductor está detrás del
329trait `IntentTranslator`: `MockTranslator` (catálogo YAML intent→`.swm`, para CI byte-a-byte) y
330`ClaudeTranslator` (feature `llm-claude`, ureq+rustls). `Orchestrator::run_with_repair` drena
331eventos del bus tras cada apply; si llega `Crashed`, formatea una intención nueva y reentra hasta
332`max_attempts`, y el `Proposal` final lleva la `repair_chain` completa.
333
334**Mini-lenguaje de consulta** (`takana query kind:value`): `bin`, `file`, `pin`, `service`,
335`depends` — con parser ELF64 mínimo para extraer `DT_NEEDED`.
336
337---
338
339## 6. Modelo de confianza (SDD 09)
340
341```
342fuente pública (repo@commit fijado) + patch declarado
343        → build determinista en TU lab local → artifact_hash
344        → ¿coincide con el expected_hash del autor?
345             sí → obtuviste su binario sin descargarlo
346             no → algo difiere (pin, patch, toolchain); takana dice qué
347```
348
349La firma Ed25519 cubre **autoría e integridad**, no autoriza promover: `apply` **siempre** reproduce
350y compara, firme o no. TrustStore local en `/var/lib/hammer/trust/*.ed25519.pub`; estados
351`trusted`/`unknown-key`/`bad-sig`/`unsigned`. El **log de transparencia** es opt-in y su embrión ya
352existe: `bootstrap.json` (SDD 11 §4).
353
354Requisitos prácticos de reproducibilidad: toolchain fijado (ya en el hash), commits fijados, sandbox
355hermético, y eliminación activa de no-determinismo. **Cuando un build no reproduce es un bug a
356corregir, no una excepción a tolerar.**
357
358---
359
360## 7. Bootstrap y auto-alojamiento (SDD 11, 12; ADR 0007, 0008) — **CERRADO**
361
362```
363host (sólo kernel + semilla pinned)
364 └─ Stage 0: toolchain semilla (zig) ingerido al store, sha256 fijado   ✅
365 └─ Stage 1: userland mínimo cross-compilado (musl, busybox, hammerd, arje-zero)  ✅
366 └─ Stage 2: rebuild NATIVO dentro del rootfs Stage 1 y comparación de of_tree     ✅
367```
368
369Hitos verificados:
370
371- **Stage 1 bootea en QEMU** con **arje-zero como PID 1** (init propio, del monorepo hermano
372  `tawasuyu`) leyendo su seed card `/ente/seed.card.json` y supervisando hammerd + getty. El
373  **`CRASHED` real** que la Fase 5 había diferido está demostrado: `kill hammerd` → arje detecta
374  `Killed(SIGTERM)`, aplica backoff y re-encarna.
375- **Stage 2 `✓ REPRODUCIBLE`** host↔VM: `of_tree(stage1') == of_tree(stage1)`.
376- **Auto-alojamiento puro (variante b) CERRADO**: el toolchain del builder lo construye takana desde
377  fuente. Los **6 swaps** (`make`, `busybox`, `coreutils`, `bwrap`, `linux-headers`, `rust`)
378  corrieron **juntos in-VM** dando `✓ REPRODUCIBLE` (`of_tree = 7fa6cb4e…`). La cadena de Rust es
379  purista: `mrustc → rustc 1.90.0 → 1.91.0 → 1.91.1` con `x.py` real, reusando un LLVM 20.1.8
380  externo.
381- **Kernel from-source CERRADO**: Linux 6.16.12 construido por takana bootea la VM del
382  selfhost-verify y reproduce bit a bit. `flex`, `bison`, `openssl`, `elfutils` (sólo libelf, para
383  objtool) de-Alpinizados como build-deps del kernel.
384
385**Los dos no-determinismos que hubo que cazar** (valen como lección general):
3861. `codegen-units=1` **NO bastaba**: el backend paralelo de rustc/LLVM (ThinLTO) divergía ~62 KB en
387   `arje-zero` a >1 CPU. Fix: `CARGO_BUILD_JOBS=1` serializa el jobserver ⇒ reproducible e
388   independiente del nº de CPUs.
3892. El baseline "bueno" anterior (`0039b2b9…`) era un build paralelo no fiable. **Dos números
390   iguales de un fallo son señal, no coincidencia**; y un baseline no verificado es una foto vieja
391   con aspecto de dato fresco.
392
393---
394
395## 8. Release engineering (SDD 13) — imágenes, instalador, mirror, upgrades
396
397- **E1 imagen auto-booteable** (GRUB BIOS i386-pc, instalado en userspace sin root con
398  `grub-mkimage` + `grub-bios-setup` sobre el fichero imagen). Layout GPT: BIOS-boot / `/` /
399  `/store` / `/var/lib/hammer`.
400- **E2 instalador a disco físico**: `takana-install <dev>` corre dentro del live como root real
401  (MBR, ext2 montable como ext4, GRUB por dos `dd`). Autoinstalador: copia la propia raíz del live.
402- **E3 mirror del store** (`takana mirror push|pull|status`): CAS replicado; el receptor
403  **recomputa `of_tree`** y exige que case antes de sellar ⇒ una transferencia corrupta se rechaza.
404- **E4 upgrades con generaciones**: apply in-place fichero-a-fichero (escritura a temporal +
405  `rename`), `current` como commit-point, `backup/` con los bytes previos, rollback exacto,
406  `prune`, y **`pending.json`** escrito ANTES de proyectar para que `recover` complete
407  (roll-forward) o deshaga (roll-back). `hammer-recover` (binario estático) lo hace **al arranque**,
408  antes de encarnar a arje-zero, y nunca aborta el boot.
409- **E5 ISO/medio live**: híbrido BIOS+UEFI (dos El Torito), initramfs = product-rootfs `cpio.gz`,
410  todo desde RAM. **Dogfooding**: el host no traía `xorriso` ni `mtools` ⇒ los construye takana.
411- **Arranque soberano EFI-stub** sin GRUB (ADR 0010), con cero parpadeo validado por captura de
412  framebuffer.
413
414---
415
416## 9. Arranque por grafo (ADR 0010) — la frontera con `tawasuyu`/mirada
417
418El menú de arranque **no es una lista de kernels**: es la **navegación del grafo de estados** del
419sistema. takana publica `/run/hammer/boot-graph.json` (nodos = generaciones/snapshots/base/recovery,
420identificados por BLAKE3, con `parents`, `bootable`, `label`); **mirada** (compositor Wayland de
421tawasuyu) lo dibuja sobre KMS y escribe la elección en `/run/hammer/boot-select`; arje-zero (PID 1)
422corre `takana boot activate --from-select` **incondicionalmente en su secuencia de apagado** — `/run`
423es tmpfs, así que la selección se aplica en el apagado o no se aplica nunca.
424
425Invariante de UX compartido: **cero parpadeo** de DRM desde la firmware hasta el escritorio (mirada
426sostiene el DRM master y nunca re-modesetea; por eso mirada real **nunca sale** y el subcomando
427`takana boot menu` es sólo harness de dev).
428
429---
430
431## 10. El armador de kernel (SDD 22) — implementado
432
433Verbo `takana kernel {stats, probe, bundles, closure, plan, diff-back, gate, hw}`.
434
435Los dos hechos que ordenan el diseño:
436
4371. **El config ES la identidad del artefacto** (vive en la fase `configure`). A favor: un "config
438   atestado por huella de hardware" sale casi gratis (es un `ArtifactHash` + una firma). En contra:
439   la explosión combinatoria es aritmética visible en el disco ⇒ **una perilla de la UI no es un
440   parámetro de runtime, es una edición de receta**, y `kernel plan` debe **emitir una receta
441   derivada**.
4422. **La clausura por `depends on` NO cierra: `select` es el portillo.** Kconfig tiene al menos tres
443   tipos de arista (`depends on` hacia arriba, `select` hacia abajo y forzada, `imply` blanda) con
444   semánticas incompatibles. Hay corpus de verdad-de-campo contra el que validar: el `configure` de
445   `linux.toml` **ya es** un juego de bundles escrito a mano (`-d WLAN -d WIRELESS -d CFG80211…`) y
446   ese kernel arranca.
447
448Añadidos propios del SDD: **bisección sin recompilar** (kernel superset con lo bisecable como `=m`
449⇒ 6 reinicios en vez de 6 rebuilds × 45 min) y **gate de no-regresión por objetivo**, no global (el
450kernel de QEMU apaga USB/HID a propósito; un gate global rechazaría una receta sana).
451
452Probado de punta a punta el 2026-08-11: kernel a medida de `gioser`, bzImage **11,1 MB vs 14,5
453(−23%)**. Y el lab está literalmente DENTRO del artefacto: Kconfig sondea el `rustc` del entorno y
454graba `CONFIG_GCC_VERSION`/`CONFIG_RUSTC_VERSION` en el `.config`.
455
456---
457
458## 11. Los escritorios (cuatro imágenes, todas desde fuente)
459
460| Frente | Estado | Notas clave |
461|---|---|---|
462| **mirada** | perfil 33/33 *(medido)* | Compositor Wayland propio (tawasuyu) + greeter, sobre mesa software o iris |
463| **KDE Plasma 6** | perfil `escritorio-kde` **163/163** *(medido)* | Hidratado rebuild-free vía `takana hydrate <hash> --into` (install-reproduce no escala). ADR 0011 |
464| **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 |
465| **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 |
466| **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 |
467
468**Stack gráfico (SDD 14):** Mesa **24.0.9** iris-only, sin LLVM y sin Vulkan. El pin es duro: desde
46924.1 `iris` exige `intel-clc` ⇒ libclc + clang/LLVM. Debajo: libdrm, wayland (+ el parche
470`wayland-scanner-dup2-output` — bajo musl dinámico `freopen(@OUTPUT@)` trunca el header por la
471copy-relocation de `stdout`), wayland-protocols, seatd, libinput/libevdev/mtdev/libudev-zero,
472libxkbcommon, pixman.
473
474**La lección transversal de este bloque, y probablemente la más importante del proyecto:**
475
476> **Sellado ≠ arranca.** El perfil `escritorio-sway` cerró **121/121, falta 0** — y entre eso y una
477> pantalla con algo dibujado hubo **seis muros y ocho ciclos de imagen**: `PATH` vacío, `libz.so.1`
478> ausente del rootfs, un backend de libseat que nuestra receta no construye, `/run` de sólo lectura,
479> los datos XKB ausentes, y falta de tipografías. **Ninguno era una dependencia de build**, así que
480> ninguno podía aparecer en el grafo. Y el método: **el veredicto es contar colores del PPM, no leer
481> el log** — hubo un arranque con todo verde en el log y la captura 100 % negra.
482
483---
484
485## 12. Infraestructura operativa (lo que no está en los SDD pero gobierna el día a día)
486
487### 12.1 Topología
488
489- **Hub principal**: laptop del autor + un segundo hub `gioser.net` (server Hetzner **protegido, sin
490  backup**, mismo proyecto hcloud que la granja). El store vive en un **volumen** dedicado
491  (bind-mount de `store`, `work/sources`, `work/repos`, `CARGO_HOME`).
492- **Granja efímera**: workers hcloud on-demand (`farm-up` / `farm-run` / `farm-down`, baseline €0),
493  arrancados desde un snapshot **golden** que trae el toolchain + el store horneados ⇒ cache-hit
494  instantáneo. El worker es **compute puro y sin secretos** (ni firma ni gitea). Lleva un *dead-man*
495  que lo **borra** (no lo apaga — apagado seguiría facturando) tras 1 h idle.
496- **Latido**: un cron cada 30 min cosecha el worker, siembra recetas y regenera el grafo. Los
497  commits `estado: cosecha granja <ts>` del historial son ese latido.
498- **Respaldo**: Storage Box BX11 (1 TiB), interrumpible y continuable; sube primero el *cerebro*
499  (estado + repo) y después el store.
500- **Espejo**: `origin` empuja a gitea **y** a un espejo privado de GitHub.
501
502### 12.2 El khipu y la yupana — la metodología de medición
503
504Distinción explícita y arquitectónica, no adorno:
505
506- **`docs/state/build-state*.json` = el KHIPU**: el registro firme y versionado (nudos = recetas,
507  cuerdas = deps). Fuente única de verdad de QUÉ hay y QUÉ falta. **Se regenera**
508  (`scripts/build-state.py`); un generado que no se regenera es una foto vieja con aspecto de dato
509  fresco (error real: KDE reportaba 162/162 cuando estaba en 76/162).
510- **`scripts/yupana.py` = la YUPANA**: el motor que reckona sobre el khipu. Es la **puerta única** de
511  la metodología. Verbos: `radio <receta>` (¿a quién rompo si toco esto?), `perfiles`, `duplicados`,
512  `keystones`, `preflight`, `estado`, `objetivo`, `frontera`, `triaje`, `drenar`.
513
514**La regla que la justifica:** *qué nodos puntúo es una decisión de vista; **quién me consume es un
515hecho** y nunca depende de la vista.* Nació de un fallo real: un cambio a `libdrm` invalidó 118
516recetas KDE, y el radio medido sólo contra `recipes/*.toml` dio **8**. Las dependencias inversas se
517calculan SIEMPRE sobre todas las colas.
518
519**Resolución sibling-first**: una dep `libdrm` desde `incoming-kde` resuelve a `incoming-kde/libdrm`
520si existe, y si no cae a `corpus/libdrm` — igual que el sandbox al apilar capas. Por eso los nodos
521se identifican por **par `(cola, nombre)`**, no por nombre.
522
523Otros órganos: `targets.py` (perfiles → lista), `drenar.py` (orden de masticado en ondas),
524`triaje.py`, `seed-graph.py --frontera`, `affected.py` (CVE por grafo), `why-differs.py`,
525`verificar-repro.sh`, `store-gc.sh`, `licencias*.sh`, `fuentes/mirror-*` (ADR 0013).
526
527### 12.3 `targets.toml` — el manifiesto de OBJETIVO
528
529`build-state.json` es el grafo de lo que EXISTE; `targets.toml` es lo que la distro **debe** tener.
530La inversión clave: `paquetes` lista **sólo las raíces** (lo que un usuario pide por nombre); la
531clausura la calcula el grafo. Siete perfiles: `base`, `cli`, `escritorio-mirada`, `escritorio-kde`,
532`escritorio-gnome`, `escritorio-cosmic`, `escritorio-sway`. Un `perfil` = una imagen enviable;
533`hereda` compone; `cola` dice en qué árbol viven sus raíces.
534
535### 12.4 ADR 0013 — mirror de fuentes (obligación GPL, y sale barato)
536
537**La URL no entra en `hash_inputs`; sólo el `sha256`/commit** ⇒ los mirrors son gratis. Cerrado:
538tarballs por sha256 (1,7 G) + git por bundle shallow (570/571, 2,3 G). Detalles pagados: 4 pines no
539son commits sino objetos **tag** (hay que pelar con `^{commit}`); `github.com` es el 64 % del
540corpus. Regla dura: **nunca cambiar el `sha256` para tapar un 404**. Vigía activo:
541`fuentes-vigia.json` → **1172 fuentes, 1172 vivas, 0 muertas** *(medido)*.
542
543---
544
545## 13. Estado real medido (2026-08-28)
546
547### 13.1 Corpus
548
549| Métrica | Valor *(medido)* |
550|---|---|
551| Recetas `.toml` activas | **1172** (+25 aparcadas en `*/.deferred`) |
552| — corpus (`recipes/`) | 787 |
553| — `incoming-kde` / `-gnome` / `-cosmic` / `-gnome-onda2` / `-wlr` / `-onda1` | 205 / 90 / 47 / 22 / 15 / 6 |
554| Parches `.patch` en el árbol de recetas | 123 |
555| Recetas con campo `license` | **1096 / 1197 (92 %)** |
556| Recetas con `compiler = "gcc"` | **36** (16 en el corpus, 20 en las colas) |
557| Recetas con `strip_debug` (campaña SDD 23) | **35**, todas en el corpus |
558| Recetas que pinean `zig_version = "0.13.0"` | 84 |
559| Entradas en el store local | 1654 (55 G en este disco; el store real vive en el volumen) |
560| Fuentes vivas (vigía) | 1172 / 1172, 0 muertas |
561
562### 13.2 Grafo de estado, por vista
563
564| Vista | nodos | sellados | deuda |
565|---|---:|---:|---:|
566| corpus | 787 | 711 | **75** |
567| KDE (`--kde`) | 978 | 899 | 74 (+4 `wanted`) |
568| GNOME | 861 | 783 | 74 (+4 `never`) |
569| COSMIC | 833 | 757 | 75 |
570| wlr | 801 | 726 | 74 |
571
572Deuda del corpus por clase: **go 37 · rust 26 · c 12 · gui 1**.
573
574**Perfiles** *(medido)*: `base` 52/52 · `cli` 75/75 · `escritorio-mirada` 33/33 ·
575`escritorio-kde` **163/163** · `escritorio-sway` **129/129** · `escritorio-cosmic` 89/90 ·
576`escritorio-gnome` 124/127.
577
578### 13.3 Lectura de la deuda actual (importante para no malinterpretar los SDD 19/20)
579
580Los SDD 19/20 (2026-08-07) hablan de **«12 recetas en deuda»**. Hoy el corpus marca **75**, y **no
581es una regresión de calidad**: la lista de nombres en deuda va alfabéticamente desde
582`llimphi-counter`/`mise` hasta `zola` — es la **cola pendiente de la campaña de reconstrucción del
583corpus en serie** (`scripts/reconstruir-corpus.sh`, reanudable, alfabética), disparada por los dos
584eventos que movieron el `ArtifactHash` de todo el catálogo a la vez:
585
5861. **El lab entró en `hash_inputs`** (2026-08-10, §3.3).
5872. **El split de debug** (SDD 23, `strip_debug`), que cambia la fase `install`.
588
589Y el diagnóstico de «las 12» originales fue **desmontado** después: 6 estaban SUPERADAS por la
590cadena dinámica de GNOME, 1 cerrada (`wlr-randr`: le faltaban `python3` —meson es un script de
591Python— y `libffi`, que exige `wayland-client.pc` en su `Requires`), 1 es frente propio
592(`elfutils-libdw`, ya cerrado: dwarves construye) y 3 son HUB-ONLY estructural (fuente por SSH a
593gitea privado; el worker es sin secretos por diseño). El hallazgo de fondo fue de **encolado, no
594técnico**: `recipes/` (el corpus) no estaba en la lista `QUEUES` del bucle del worker — nadie las
595estaba intentando.
596
597---
598
599## 14. Deuda abierta y problemas sin resolver
600
601### 14.1 ADR 0012 — la carrera del árbol de fuentes (**PENDIENTE, sin decidir**)
602
603`fetch` nombra el árbol de forma determinista y **sin componente por-constructor**:
604`work/sources/<nombre>-<sha16>`. Dos builds de la misma dep resuelven al MISMO directorio; uno hace
605`remove_dir_all` mientras el otro corre `tar -x` y **el árbol queda roto para siempre**.
606
607El árbol cumple **dos papeles que se contradicen bajo concurrencia**: caché direccionada por
608contenido *y* workspace mutable de build (patches, `cargo vendor`, `go mod vendor` — minutos y
609decenas de GB). Por eso **un lock sólo en la extracción parece correcto y NO lo es**: un segundo
610proceso puede borrar un árbol que un `bwrap` está usando para compilar.
611
612Medido a escala: al invalidar `libdrm`, ~93 de 205 recetas KDE murieron con `/src/.zwrap/cc is not a
613full path to an existing compiler tool` — el wrapper de zig que la receta deja EN EL ÁRBOL, barrido
614por el fetch concurrente de otra receta.
615
616Mitigación vigente (no solución): **`flock work/.farm-build.lock` alrededor de todo `takana build`**,
617tomado también por `farm-worker-loop.sh` y `campana-deuda.sh`, y `JOBS=1` en el worker. Opciones
618sobre la mesa, ninguna elegida: (A) lock por árbol sostenido durante todo el build —correcto pero
619serializa el diamante que converge en qtbase/kcoreaddons—, (B) árbol privado por constructor, (C)
620caché inmutable + copia.
621
622### 14.2 No-determinismo en `.debug_*` (SDD 23)
623
62492 paquetes con no-determinismo probable. La causa medida **no** son rutas del host sino **rutas
625internas al árbol de build que varían entre corridas** (`/src/output/meson-private`). Verificado con
626`takana why-differs`: `binutils` y `wl-clipboard` reproducen; `appstream` y `bison` **divergen**, y
627la divergencia vive ENTERA en secciones `.debug_*` (el código ejecutable es idéntico).
628
629**La buena noticia estructural:** las dos mitades del plan son el mismo trabajo — **separar el debug
630hace que el artefacto principal reproduzca**, sin tocar `-ffile-prefix-map` en ~720 recetas. Y el
631tamaño: ~60 % del *artefacto* es información de depuración (el «79 %» que se citaba medía el
632*contenido binario*, no el artefacto) ⇒ el espejo público baja de ~126 G a ~51 G.
633
634**Estado de la campaña** *(medido)*: sólo **35 recetas** declaran `strip_debug`, todas en el corpus.
635Es decir: el mecanismo está implementado y el plan escrito, pero el barrido está en su arranque —
636y cada tanda re-hashea lo que toca, así que compite por la misma granja que la reconstrucción del
637corpus (§13.3).
638
639### 14.3 Otros frentes abiertos
640
641- **36 recetas con `compiler = "gcc"`** *(medido: 16 en el corpus + 20 en las colas de escritorio;
642  contadas parseando TOML, porque `grep` cuenta comentarios)*. Las 12 de Rust son **un solo
643  problema**: falta el unwinder de libgcc (perilla `LIBS=-lunwind` ya identificada).
644- **Techo MSRV del sandbox = 1.96.** La frontera son los `*-sys` con C++ — y es **exactamente** lo
645  que bloquea Firefox. *Antes del navegador hay que subir el techo.*
646- **`*-sys` que piden libs C sin `[deps]`**: `unable to find static system library 'lzma'/'bz2'` en
647  recetas Cargo. Arreglo por receta (`xz`, `bzip2`, `sqlite`), **nunca engordar el rootfs**, y
648  **nunca en bloque**: hay ~220 recetas Cargo sin deps y re-hashearlas invalidaría las selladas.
649- **Multi-init**: los inits del catálogo (openrc, etc.) **compiten** con arje-zero, no se apilan.
650- **`link=static` es a veces mentira**: cuatro causas distintas (libtool se come el `-static`; el
651  Makefile lo pisa; `link` es INERTE en Go; la receta Cargo pisa el lab y pierde `crt-static`).
652  Auditado: 0/680 mienten hoy, pero el audit **sobre-reporta si no se re-sella antes**.
653- **Licencias**: 1096/1197 declaran; faltan ~101 y hay que desambiguar SPDX obsoletos (`GPL-3.0` no
654  dice si `-only` u `-or-later`). Regla dura: **no se rellena a ojo** — declarar mal es peor que
655  dejar vacío, porque convierte un hueco visible en una afirmación falsa. El cierre definitivo es
656  capturar la licencia en la **fase de fetch**, donde el dato pasa gratis.
657- **Falta para publicar** (SDD 19/20/21): espejo de fuentes servido, **clave raíz fuera de línea**
658  (lo único no delegable a un agente), imagen validada en **metal** con pantalla, upgrade N→N+1 con
659  rollback probado, `cups` y `bluez`, documentación mínima y canal de reportes.
660- **Aplicaciones gráficas de terceros: cero.** Barato: imv, zathura, mpv, gestor de archivos.
661  Caro: LibreOffice. El más caro del catálogo: un navegador.
662
663### 14.4 Modos de fallo estructurales que este proyecto descubrió (y que valen como principio)
664
6651. **Un artefacto VACÍO es un cache-hit.** `takana build` miraba presencia del directorio, no
666   contenido ⇒ sellaba sin construir y salía OK. Siete vacíos se propagaron de respaldo →
667   manifiesto → grafo → store → build: **en cada eslabón el vacío se lee como presencia**. Regla:
668   *un ausente falla ruidosamente; un vacío llega hasta el final diciendo que todo fue bien.*
6692. **El cache-hit congela regresiones.** «No re-hashea nada sellado» ≠ «es seguro»: una regla del
670   wrapper llevaba meses rompiendo `make` sin que se viera, porque el artefacto viejo se servía por
671   caché. Al reconstruir: exit 139 en medio corpus. ⇒ **tras tocar el harness, reconstruir a
672   propósito una receta de cada tipo.**
6733. **Con flota efímera y sync unidireccional, un escritorio puede dejar de ser
674   reconstruible-desde-el-store sin que ningún indicador lo diga.** Pasó con KDE: los workers donde
675   vivían esos artefactos ya no existen, el sustrato cambió, las sombras se re-hashearon y nadie las
676   reconstruyó. El grafo seguía diciendo 162/162 en un JSON generado semanas antes.
6774. **`grep` no es una medición en este repo.** Contó comentarios en el conteo de licencias
678   (`grep -l license` → 5 falsos positivos), contó `IO_URING` en un comentario del kernel, y contó
679   `.rela.debug_*` en la regla del `.a` no-PIC (`readelf -r | grep R_X86_64_32` **miente** si no se
680   excluye debug). El veredicto se obtiene **reconstruyendo y comparando**, no buscando cadenas.
6815. **`.dmerge` retiene lo que borrás del store** (hardlinkea los artefactos) ⇒ podar el store no
682   libera disco mientras la caché los referencie. Medir con `st_nlink==1`, **nunca con `du`** (suma
683   los hardlinks otra vez).
684
685---
686
687## 15. La frontera (SDD 15, 17, 18) — lo diseñado pero no cerrado
688
689### 15.1 SDD 15 — proof-carrying, cómputo como dato, código por contenido
690
691- **H1 — Proof-carrying recipes ✅**: el `.swm` lleva un bloque `evidence` (`{kind, cmd, expected}`,
692  `kind ∈ {cmd-exit, proptest, contract, kani}`); `takana swm-verify --evidence` corre cada ítem
693  **dentro del sandbox reproducible**. La evidencia corre del lado del BUILD (separación
694  PROPONE/CONSTRUYE: `takana-agent` no depende de `takana-build`); `Event::BuildReady` lleva el
695  `EvidenceVerdict` y el `Orchestrator` **aborta antes de hidratar** si falla. **Frontera honesta
696  declarada**: garantiza que *lo declarado pasa*, no que *lo declarado basta*.
697- **H2 — Memoización de ejecución ✅ (en `wawa-memo`, repo hermano)**: si el wasm es determinista,
698  `blake3(módulo) + blake3(entrada) + E → blake3(salida)` es una función pura y su caché es un
699  `LwwMap` monótono compartible por anti-entropy. `E` es un hash-de-entorno que pinea
700  wasmi/features/fuel/mem/ABI. Único vector residual acotado: payload de NaN vía `reinterpret`.
701- **H3 — Código por contenido estilo Unison ✅ (subconjunto)**: `Base` content-addressed de
702  funciones wasm — identidad = `blake3(bytecode)`, nombres como metadata separada, **actualizar no
703  rompe** (v1 y v2 coexisten), composición por hash. **ADR 0009 decide NO reescribir la unidad de
704  compilación**; el puente barato es procedencia por-símbolo como metadata.
705- **H4 — Configuraciones compartidas ✅**: el eje nuevo son los **slots**. Una modificación
706  **reclama** (`claim: slot→Id`) y **requiere** (`requires: slot→Id`). *Compatible* queda definido
707  duro: requisitos resueltos + reclamos disjuntos. Dos casos que el modelo trata distinto: **logo**
708  (dos configs escriben la misma superficie ⇒ **colisión = elección**) y **wayland** (dependencia
709  que no resuelve ⇒ **rechazo**). Subido a la receta real (`[slots]`, fuera de `hash_inputs`) y a la
710  `InstalledDb` (`system_state() → slot → hash`). CLI: `takana compat`.
711
712**La ambición declarada al final del SDD 15**: `config = paquete = función = proceso`, todos el
713mismo tipo de objeto direccionado por el mismo hash.
714
715### 15.2 SDD 17 — los diez cierres de frontera, priorizados
716
717**Tesis:** los tres invariantes ya pagados —**bit-reproducibilidad**, **direccionamiento por
718contenido** y **clausura-como-política**— hacen casi gratis para takana lo que a otros les cuesta.
719La búsqueda correcta no es «qué frontera agregar» sino **«qué cae solo del invariante»**.
720
7211. **Consenso de reconstrucción** — N builders independientes reproducen el mismo hash ⇒ el binario
722   es confiable **sin que nadie firme nada**. Convierte la repro de QA en primitiva de distribución:
723   cualquiera puede ser mirror, nadie puede envenenar el repo. Nix/Debian no pueden ofrecerlo.
7242. **`why-differs`** (diffoscope propio) — **implementado**: nombra la causa (un MTIME en un gzip,
725   la cabecera `ar` de un `.a`, qué sección ELF cambió, una ruta filtrada). Multiplicador de todo lo
726   demás.
7273. **Política de runtime derivada del closure** — fase 3 de harkaq. Con **dos correcciones medidas**:
728   (a) la clausura de build **no puede** ser la política de runtime porque le **sobra** casi todo
729   (un `htop` musl-estático bajo la jaula **no tocó un solo fichero** fuera de sí mismo; derivarla
730   del build le habría concedido headers y compilador, **firmados**) ⇒ la política granular **se
731   MIDE corriendo el binario**; (b) la **lección casper**: hay una clase —*servicios del mundo*—
732   que no son paths y no caben en ninguna jaula por-fichero, y el build offline no la contiene ⇒
733   **frontera** (clases `dns`/`tls-certs`/`random`/`locale-tz`/`syslog`, **bits de un `u32`
734   firmado** de 36 bytes canónicos) + **detalle** (paths, Landlock, medidos).
7354. **CVE por grafo** — `takana affected CVE-X` exacto + frontera mínima de rebuild + hydrate para
736   repartir el parche rebuild-free. Donde Debian/Alpine aproximan por nombre-versión, takana
737   responde exacto.
7385. **Sellar el arranque** — verity + medida; el store CAS hace fs-verity casi gratis.
7396. **Matar el trusting-trust: DDC** — con la cadena mrustc cerrada, takana es de los poquísimos que
740   puede **ejecutar** Diverse Double-Compiling. Un fin de semana de granja, resultado publicable.
7417. **De-Alpinizar el rootfs del sandbox** — canal impuro **estructural** mientras el sandbox se
742   apoye en Alpine; harkaq lo mide.
7438. **`takana oci`** — imágenes distroless bit-reproducibles desde un closure. Vector de adopción
744   realista.
7459. **Updates diferenciales content-defined** (chunking estilo casync sobre el store).
74610. **Bucle agéntico con juez mecánico** — LA tesis AI-nativa: *el catálogo se cultiva solo porque
747    el juez es mecánico*. Sin juez, las recetas de un LLM son deuda; con él, son cosecha. Ya
748    demostrado a mano con `zlib` (Impuro→Hermetico en 3 fases, guiado sólo por harkaq).
749
750**Qué NO priorizar** (dicho explícitamente): solver SAT de versiones (el grafo es curado), multi-arch
751(esperar demanda), config declarativa estilo NixOS (cancha ajena).
752
753### 15.3 SDD 18 — wawafs: el store como filesystem del sistema vivo
754
755**Diseño aceptado, sin implementar.** Bajar el checkout de userspace al kernel con la pila
756**composefs** (CAS + erofs + overlayfs + fs-verity, todo mainline): no hay que escribir un
757filesystem. Capas: proceso → VFS (rutas FHS reales, sin pueblo fantasma) → composefs mount →
758manifest erofs por clausura (KB–MB, cero datos) → CAS de objetos → fs-verity opcional → ext4.
759
760Propiedades que caen solas: dedup de disco **y de page cache**, activar/rollback **O(metadata)**,
761inmutabilidad estructural, y **bit-repro en runtime** (el kernel verifica el Merkle en cada lectura;
762un bit podrido = EIO, no un binario silenciosamente distinto).
763
764**Identidad: dos granos, un mapa.** blake3 sigue siendo la identidad (recetas, sellado, grafo); el
765digest fs-verity (sha256) es el *enforcement*. Misma jugada que ADR 0009.
766
767ADR 0005 (hardlinks) **sigue vigente** para el laptop de desarrollo; wawafs es el modo **despliegue**
768(ro). El corte cae donde el workload cambia de naturaleza: se escribe una vez al sellar, se lee mil
769veces al vivir. Prototipo medido (2026-07-17): `hydrate` 28 ms vs `mkcomposefs` 507 ms sobre un
770rootfs de 262 MB; manifiesto `.cfs` de **136 KB firmables**. Hallazgo: **con hardlinks, un write con
771privilegios a través del árbol hidratado corrompe el store** (comparten inode); con composefs es
772imposible por construcción. Precondición: el kernel propio trae OVERLAY_FS+REDIRECT_DIR+METACOPY
773pero **falta `EROFS_FS` y `FS_VERITY`** ⇒ re-sellado de kernel.
774
775---
776
777## 16. Los ADR, con su estado
778
779| # | Decisión | Estado |
780|---|---|---|
781| 0001 | Rust para el tooling y los daemons | aceptada |
782| 0002 | Validar sobre Alpine antes de la distro propia | aceptada |
783| 0003 | `zig cc` como compilador por defecto del lab | aceptada |
784| 0004 | **No escribir nuestro propio Nix** | aceptada |
785| 0005 | Hidratación por hardlinks | aceptada (convive con wawafs como backend futuro) |
786| 0006 | Commits fijados, no `HEAD` vivo | aceptada |
787| 0007 | `arje` como init propio del track posterior | propuesta (implementada de facto) |
788| 0008 | Bootstrap en 3 stages, `zig` como semilla | aceptada |
789| 0009 | Código direccionado por contenido (Unison): registrar la visión, no reescribir | propuesta / design-doc |
790| 0010 | Arranque por grafo: el menú de boot como navegación del grafo | aceptado; lado takana 1–5 ✅ |
791| 0011 | Etapa Escritorio: campaña KDE Plasma 6 | aceptado; gates H-Qt y H-QML pasados |
792| 0012 | El árbol de fuentes es caché y workspace a la vez | **PENDIENTE, sin decidir** |
793| 0013 | Mirror de fuentes: la URL es transporte, el `sha256` es la identidad | ACEPTADO, implementado |
794
795---
796
797## 17. Superficie del CLI (`takana`)
798
799```
800build · hash [--check] · why-differs · attest · hydrate
801try · commit · discard · status · journal
802apply · swm-verify [--evidence] · swm-sign · keygen · export
803pack · install · uninstall · installed · compat · repo {list,sign,verify}
804import-nix · import-alpine · pin
805ai [--catalog|--llm] [--repair-max-attempts] · ctl · query
806bootstrap {stage0,stage1,stage2,builder,all,manifest}
807mirror {push,pull,status}
808upgrade {apply,rollback,recover,status,prune}
809boot {graph,activate,menu}
810kernel {stats,probe,bundles,closure,plan,diff-back,gate,hw}
811```
812
813Daemon: `hammerd [--journal DIR] [--agent-caps FILE]`.
814
815---
816
817## 18. Reglas del repo (CLAUDE.md) — no son estilo, son seguridad
818
8191. **Todo `takana build` va envuelto en `flock work/.farm-build.lock`.** Por el ADR 0012. Para una
820   tanda, tomar el lock **una sola vez**, no por receta. El lock está **fuera** de `takana build` a
821   propósito: los scripts de granja lo toman por fuera y takana se bloquearía contra ellos.
8222. **Nunca `git add -A`.** Varios agentes trabajan el repo a la vez; un `add -A` arrastra ficheros a
823   medias de otro. Commits granulares, en español, directo sobre `main`, y `git push` tras cada
824   unidad de trabajo.
8253. **Antes de dar un artefacto por presente, mirá que tenga contenido.** (Ver §14.4.1.)
826
827---
828
829## 19. Vocabulario del proyecto (para leer commits y docs sin perderse)
830
831| Término | Qué es |
832|---|---|
833| **el lab / el sótano** | El laboratorio de build hermético |
834| **sellar** | Depositar un artefacto en el store bajo su hash |
835| **hidratar** | Proyectar un artefacto del store al FHS |
836| **la granja** | La flota efímera de workers hcloud |
837| **el hub** | La máquina con secretos (laptop o gioser); firma, gitea, clasifica |
838| **el latido** | El cron */30 que cosecha, siembra y regenera el grafo |
839| **cola / queue** | Un árbol de recetas: `corpus` = `recipes/`; el resto `recipes/incoming-<x>/` |
840| **sombra** | Una receta homónima en otra cola que gana por sibling-first |
841| **deuda** | Nodo del grafo sin artefacto en su hash **vigente** |
842| **drenar** | Construir la deuda en ondas topológicas |
843| **frontera** | Lo que upstream pide y no tenemos |
844| **keystone** | Nodo en deuda que gatea el máximo trabajo bloqueado |
845| **radio** | Cuántos nodos rompo si toco una receta |
846| **superado / huérfano** | Artefacto con ejemplar mejor (podable gratis) / ejemplar único |
847| **de-Alpinizar** | Quitarle a una receta la dependencia implícita del rootfs Alpine |
848| **harkaq** | La jaula (Landlock+seccomp) que verifica que lo declarado basta |
849| **yupana / khipu** | El motor de cálculo sobre el estado / el registro firme del estado |
850| **arje / arje-zero** | El init propio (PID 1) del monorepo hermano `tawasuyu` |
851| **mirada** | El compositor Wayland de tawasuyu que dibuja el menú de arranque |
852| **wawa / wawafs** | El runtime wasm de tawasuyu / el frente de store-como-FS |
853
854---
855
856## 20. Dónde hay hueco para SDDs nuevos
857
858Esto es lectura del autor de este informe, no doctrina del repo. Los huecos donde un SDD tendría
859tracción real, con la razón por la que hoy no existe uno:
860
8611. **ADR 0012 resuelto → SDD del modelo de árbol de fuentes.** Es la única decisión estructural
862   marcada PENDIENTE, bloquea el paralelismo del worker (`JOBS=1`) y ya rompió una campaña entera.
863   Cualquier propuesta debe cubrir **extracción + build**, no sólo la extracción, y decir qué pasa
864   con `cargo vendor` (minutos, decenas de GB) y con el diamante que converge en qtbase.
8652. **SDD del split de debug terminado** (SDD 23 está a medias): qué se hace con el paquete `-debug`
866   en sí, cómo viaja por el repo `.swm`, y si `-ffile-prefix-map` entra o no. Decide ~96 G de disco
867   y el tamaño del espejo público.
8683. **SDD de gestión de claves y cadena de release.** Hoy hay firma de índice; falta clave raíz
869   offline, rotación, procedimiento de compromiso. Es **bloqueante para publicar** y el propio SDD
870   21 lo marca como lo único no delegable a un agente.
8714. **SDD de política de runtime** (cierre §3 de SDD 17): el diseño de dos niveles ya está medido
872   —frontera como bits de un `u32` firmado, detalle medido corriendo el binario— pero no hay
873   documento que lo fije ni herramienta que lo emita end-to-end.
8745. **SDD de consenso de reconstrucción** (cierre §1 de SDD 17): el más diferenciador y el que
875   convierte la reproducibilidad de QA en primitiva de distribución. Las piezas (granja,
876   `bootstrap.json`, `fork-proof`, `umbral`) existen; falta el protocolo.
8776. **SDD de validación de imagen: de «cierra en el grafo» a «arranca».** La lección
878   *sellado ≠ arranca* costó seis muros y ocho ciclos y **no tiene documento**: hoy vive como
879   anécdota en el §II.1 del SDD 20. Un harness reproducible (contar colores del PPM, comparar
880   `NEEDED` del ELF contra el rootfs, verificar datos XKB/tipografías/`/run` rw) evitaría pagarlo
881   una vez por perfil.
8827. **SDD del catálogo de aplicaciones gráficas**, con el techo MSRV como precondición explícita: hoy
883   el navegador está presupuestado como frente propio pero sin plan escrito, y el orden correcto
884   (subir el techo → `*-sys` con C++ → Firefox) sólo aparece como nota en el SDD 21.
8858. **SDD de wawafs F0–F3** ya existe (SDD 18) pero está sin implementar y sin gate ejecutado; su F0
886   es barato (spike en QEMU con composefs de paquete) y decide si el frente existe.
8879. **SDD de la fase de fetch como capturadora de metadatos** (licencia, y por extensión SBOM): el
888   propio SDD 20 identifica que ahí el dato «pasa gratis», y cerraría la deuda de licencias para
889   siempre en vez de por barrido.
890
891---
892
893## 21. Cómo leer el repo (mapa de ficheros)
894
895```
896docs/00..23-*.md        los SDD (00 visión … 23 re-hasheo)
897docs/adr/               las 13 decisiones
898docs/runbooks/          operativos: stage1-vm-boot, self-hosting-toolchain, kde/gnome/cosmic-desktop,
899                        armador-de-kernel, harkaq-*
900docs/state/             build-state*.json (el khipu), targets.toml (el objetivo), drenaje.json,
901                        lab-toolchain.lock, kernel-bundles.toml, keystones.json, fuentes-vigia.json
902docs/evidencia/         capturas PNG que sostienen las afirmaciones de arranque
903crates/                 los 12 crates del workspace
904recipes/                787 recetas del corpus + colas incoming-{kde,gnome,cosmic,wlr,…}
905scripts/                yupana.py (puerta única), build-state.py, drenar.py, targets.py,
906                        why-differs.py, store-gc.sh, licencias*.sh, verificar-repro.sh,
907                        selfhost-verify.sh, reconstruir-corpus.sh, lab-image.sh,
908                        farm/ (30 scripts de granja), harkaq/ (19), fuentes/ (6), rust-frontier/ (8)
909store/                  el CAS (bind-mount al volumen dedicado)
910work/                   sources/, repos/, locks, logs de campaña
911```
912
913**Punto de entrada recomendado para un agente nuevo:** `CLAUDE.md` → `docs/00-vision.md` →
914`docs/01-architecture.md` → `docs/10-roadmap.md` → `docs/state/build-state.json` (regenerándolo) →
915`scripts/yupana.py radio <receta>` antes de tocar nada compartido.

BRIEFING-takana.md

takana — 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 takana 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

takana es una distribución Linux + su gestor de construcción, escrita en Rust, cuya tesis es separar dos mundos hoy fusionados:

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: takana 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)
        │
   takana-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)

CrateResponsabilidad única
takana-coreTipos compartidos: Recipe, Swm, Store, ArtifactHash, proto (bus), repo, sign, installed, compat (slots), query, differs, lab, apply, caps, y el módulo kernel/ (SDD 22)
takana-buildEl laboratorio: sandbox (bwrap), fetch, download, hydrate, swm_bridge, harkaq (la jaula), config
takana-bootstrapOrquesta stage0/stage1/stage2/builder/all + manifest (log de transparencia)
takana-overlaytry/commit/discard/status sobre overlayfs, con apilamiento LIFO
takana-journalDiario append-only JSON-líneas, content_hash, de-dup idempotente
takana-mirrorReplicación del store por hash entre máquinas (verifica of_tree al recibir)
takana-upgradeGeneraciones, apply/rollback/prune/recover, boot_graph (ADR 0010)
hammer-recoverMini-binario estático que auto-sana un upgrade interrumpido, al arranque
takana-agentAgentClient, IntentTranslator (+ MockTranslator, ClaudeTranslator), Orchestrator
takana-cliEl binario takana (+ alpine_import, nix_import, kernel_cmd)
hammerdDaemon: bus de agente, watcher fanotify, FIFO de init, crashes, arje_link
netupDHCP/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 (takana-core/src/recipe.rs):

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:

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

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

takana 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
fetchNo (red)Clona al commit / baja el tarball y verifica sha256; vendorea Cargo/Go
patchSíAplica source.patches
configureSíHeurística de build system: autotools / cmake / meson / cargo / make plano
compileSíCompila al target/link de la receta
installSímake install DESTDIR=/out o equivalente
sealNoHashea /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

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 takana-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

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

takana 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 (takana 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); takana 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:

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


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. takana 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 takana 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 takana boot menu es sólo harness de dev).


10. El armador de kernel (SDD 22) — implementado

Verbo takana 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)

FrenteEstadoNotas clave
miradaperfil 33/33 (medido)Compositor Wayland propio (tawasuyu) + greeter, sobre mesa software o iris
KDE Plasma 6perfil escritorio-kde 163/163 (medido)Hidratado rebuild-free vía takana hydrate <hash> --into (install-reproduce no escala). ADR 0011
GNOMEperfil 124/127, 3 aparcadas por diseño (medido)Keystone: el shell es una isla dinámica — gjs dlopenea .so reales ⇒ la introspección los exige. mutter→gnome-shell es HUB-ONLY estructural
COSMICperfil 89/90 (medido)4º escritorio usable, 6 apps + panel/dock, portal cerrado (captura da PNG real). Barato: no hay torre de C debajo
wlr/swayperfil escritorio-sway 129/129 (medido), arranca en QEMU10 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

12.2 El khipu y la yupana — la metodología de medición

Distinción explícita y arquitectónica, no adorno:

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étricaValor (medido)
Recetas .toml activas1172 (+25 aparcadas en */.deferred)
— corpus (recipes/)787
— incoming-kde / -gnome / -cosmic / -gnome-onda2 / -wlr / -onda1205 / 90 / 47 / 22 / 15 / 6
Parches .patch en el árbol de recetas123
Recetas con campo license1096 / 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 local1654 (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

Vistanodosselladosdeuda
corpus78771175
KDE (--kde)97889974 (+4 wanted)
GNOME86178374 (+4 never)
COSMIC83375775
wlr80172674

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 takana 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 takana 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

14.4 Modos de fallo estructurales que este proyecto descubrió (y que valen como principio)

  1. Un artefacto VACÍO es un cache-hit. takana 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

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 takana 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 — takana affected CVE-X exacto + frontera mínima de rebuild + hydrate para repartir el parche rebuild-free. Donde Debian/Alpine aproximan por nombre-versión, takana 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, takana 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. takana 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ónEstado
0001Rust para el tooling y los daemonsaceptada
0002Validar sobre Alpine antes de la distro propiaaceptada
0003zig cc como compilador por defecto del labaceptada
0004No escribir nuestro propio Nixaceptada
0005Hidratación por hardlinksaceptada (convive con wawafs como backend futuro)
0006Commits fijados, no HEAD vivoaceptada
0007arje como init propio del track posteriorpropuesta (implementada de facto)
0008Bootstrap en 3 stages, zig como semillaaceptada
0009Código direccionado por contenido (Unison): registrar la visión, no reescribirpropuesta / design-doc
0010Arranque por grafo: el menú de boot como navegación del grafoaceptado; lado takana 1–5 ✅
0011Etapa Escritorio: campaña KDE Plasma 6aceptado; gates H-Qt y H-QML pasados
0012El árbol de fuentes es caché y workspace a la vezPENDIENTE, sin decidir
0013Mirror de fuentes: la URL es transporte, el sha256 es la identidadACEPTADO, implementado

17. Superficie del CLI (takana)

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 takana 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 takana build a propósito: los scripts de granja lo toman por fuera y takana 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érminoQué es
el lab / el sótanoEl laboratorio de build hermético
sellarDepositar un artefacto en el store bajo su hash
hidratarProyectar un artefacto del store al FHS
la granjaLa flota efímera de workers hcloud
el hubLa máquina con secretos (laptop o gioser); firma, gitea, clasifica
el latidoEl cron */30 que cosecha, siembra y regenera el grafo
cola / queueUn árbol de recetas: corpus = recipes/; el resto recipes/incoming-<x>/
sombraUna receta homónima en otra cola que gana por sibling-first
deudaNodo del grafo sin artefacto en su hash vigente
drenarConstruir la deuda en ondas topológicas
fronteraLo que upstream pide y no tenemos
keystoneNodo en deuda que gatea el máximo trabajo bloqueado
radioCuántos nodos rompo si toco una receta
superado / huérfanoArtefacto con ejemplar mejor (podable gratis) / ejemplar único
de-AlpinizarQuitarle a una receta la dependencia implícita del rootfs Alpine
harkaqLa jaula (Landlock+seccomp) que verifica que lo declarado basta
yupana / khipuEl motor de cálculo sobre el estado / el registro firme del estado
arje / arje-zeroEl init propio (PID 1) del monorepo hermano tawasuyu
miradaEl compositor Wayland de tawasuyu que dibuja el menú de arranque
wawa / wawafsEl 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.