Files
takana/docs/BRIEFING-takana.md
SergioandClaude Opus 5 008dd3925e renombre: los instaladores pasan a takana-* — y el peligro no era el fichero, era el PROTOCOLO
ADR 0016 los listaba entre los CONGELADOS; el usuario pidió descongelarlos al abrir el SDD 28. La
enmienda queda escrita en el propio ADR, que si no la etiqueta deja de describir el hecho.

`hammer-install.sh` → `takana-install.sh`, `hammer-live-install.sh` → `takana-live-install.sh`,
`hammer-banner.txt` → `takana-banner.txt`, `BRIEFING-hammer.md` → `BRIEFING-takana.md`.

**Lo que hacía caro esto no es el nombre del fichero.** El instalador se inyecta en el ISO como
`/usr/bin/hammer-install` y su éxito se detecta con un `grep` de `HAMMER-INSTALL-OK` desde TRES
scripts de prueba. Renombrar un solo lado los deja casando NADA — sin fallar —, que es literalmente
el modo en que `atribuir-fallos.py` quedó mudo cuando el renombre movió el target de `tracing`.
Se renombraron las dos puntas en el mismo commit (`/usr/bin/takana-install`,
`TAKANA-INSTALL-OK/FAIL`, `TAKANA_INSTALL_*`, `work/takana-install.img`, `/run/takana-install`),
se comprobó por `grep` que no quede ningún token viejo fuera del ADR, y —lo que decide— se CORRIÓ
`install-tui-test.sh`: 4/4 casos verdes.

Las tres `TAKANA_INSTALL_*` caen al nombre viejo (`${TAKANA_INSTALL_X:-${HAMMER_INSTALL_X:-}}`):
el llamador puede ser un ISO anterior al renombre. Misma convención que `TAKANA_ROOT_PW` unas
líneas más arriba en ese mismo script.

NO se tocó `/usr/sbin/hammer-recover` ni su hook de arranque —renombrarlo rompe máquinas YA
INSTALADAS, no el repo—: sobrevive intacto dentro del script renombrado, verificado por conteo
antes y después (8 ocurrencias). Tampoco `hammerd`, `hammer-edit` (su `name` está en la ruta del
store), `/var/lib/hammer`, `HAMMER_LIVE` ni los siete literales de hash.

De paso: `scripts/.hammer-banner.txt.kate-swp` era un swap de editor commiteado por error. Fuera.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RomoxEGZUhaT4pob1QSX5x
2026-09-09 22:54:13 +00:00

54 KiB
Raw Permalink Blame History

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:

  • El sótano (el laboratorio): compilación determinista, hermética y direccionada por contenido (BLAKE3). bubblewrap + zig cc + musl. Cada artefacto es un árbol de ficheros sellado bajo su hash de entrada.
  • El piso de arriba (el userland): un Linux clásico, mutable, con FHS real (/bin, /lib, /etc). Los binarios se hidratan desde el store por hardlinks.

Entre ambos viven tres piezas propias: overlay de experimentación (try/commit/discard), diario de mutaciones por fanotify (la configuración se deriva a posteriori, no se declara antes), y el .swm, un manifiesto de mutación que viaja en vez del binario — el receptor reproduce y verifica. Encima corre un bus de agente (/run/agent.sock) por el que una IA propone, construye, prueba en overlay y presenta; el humano hace commit.

El proyecto ya cerró el bucle AI-nativo completo, el bootstrap auto-alojado bit-reproducible (incluido el toolchain y el kernel), cuatro escritorios construidos desde fuente, formato de paquete/repositorio/instalador, imágenes ISO/USB/EFI e infraestructura de granja de compilación. Lo que falta para publicar es sobre todo legal, de claves y de validación en metal.


1. Filosofía y anti-objetivos (SDD 00)

El problema. Las distros inmutables/declarativas (NixOS, Guix, Silverblue) dan determinismo a costa de agencia: el SO es el grafo, se pierde la estructura familiar y la IA tiene que simular a un humano tras capas de abstracción. Las tradicionales (Arch, Gentoo) dan control pero se pudren: el gestor pierde el rastro de lo que el usuario hizo a mano.

La tesis. «La automatización funcional es mi empleada en el sótano. En el piso de arriba mando yo.» Determinismo en la fábrica, libertad en la ejecución; ninguno de los dos se negocia.

Por qué es AI-nativo (el diferenciador real, no la distro en sí):

  1. Compilación determinista ⇒ la IA cambia fuente, recompila, y el binario es predecible.
  2. El sistema ejecutable es texto plano + binarios atómicos ⇒ nada de bases de datos binarias opacas (registro, journal binario). Se lee con cat, grep, readelf.
  3. Mutabilidad = agencia real, con overlay como red de seguridad.
  4. musl mantiene el C legible para el contexto de un LLM (glibc es un laberinto de macros).
  5. El enlazado estático evita que mutar una herramienta rompa las librerías de otra.

Principios. Verificar, no confiar · texto plano por encima de bases opacas · la IA propone, el humano dispone · no reinventar lo resuelto · el diario es la verdad (se vive imperativamente y el estado declarativo se deriva después, nunca al revés).

Anti-objetivos. No es un gestor inmutable; no reescribe un motor de build (ADR 0004: 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)

Crate Responsabilidad única
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)
takana-build El laboratorio: sandbox (bwrap), fetch, download, hydrate, swm_bridge, harkaq (la jaula), config
takana-bootstrap Orquesta stage0/stage1/stage2/builder/all + manifest (log de transparencia)
takana-overlay try/commit/discard/status sobre overlayfs, con apilamiento LIFO
takana-journal Diario append-only JSON-líneas, content_hash, de-dup idempotente
takana-mirror Replicación del store por hash entre máquinas (verifica of_tree al recibir)
takana-upgrade Generaciones, apply/rollback/prune/recover, boot_graph (ADR 0010)
hammer-recover Mini-binario estático que auto-sana un upgrade interrumpido, al arranque
takana-agent AgentClient, IntentTranslator (+ MockTranslator, ClaudeTranslator), Orchestrator
takana-cli El binario takana (+ alpine_import, nix_import, kernel_cmd)
hammerd Daemon: bus de agente, watcher fanotify, FIFO de init, crashes, arje_link
netup DHCP/netlink mínimo para el producto

2.3 Estado en disco

/store/<blake3>-<name>/     artefacto sellado, inmutable, append-only
/var/lib/hammer/
  recipes/  pins.toml  journal/  overlays/  trust/  installed.json  upgrades/
/run/init.control           FIFO de control humano (texto crudo)
/run/agent.sock             socket de agente (JSON-líneas, SO_PEERCRED)
/run/hammer/boot-graph.json grafo de estados que dibuja el menú de arranque (ADR 0010)

3. Definiciones canónicas (lo que hay que entender para proponer cambios)

3.1 Recipe — el átomo del grafo

TOML declarativo y puro. Campos reales (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:

  • hash_inputs es una LISTA BLANCA. Lo que no está enumerado no mueve el hash. Por eso license, evidence y slots se pudieron añadir sobre recetas ya selladas sin re-hashear nada. Es el truco recurrente para pagar deuda barata.
  • Las fases de build SÍ entran. ⇒ editar el -j de una fase cambia el hash aunque el binario sea idéntico; ⇒ el .config del kernel (que vive en la fase configure) es la identidad del artefacto (SDD 22 §1); ⇒ strip_debug obliga a re-hashear el corpus (SDD 23).
  • El hash es de entrada, no de contenido. takana hash <receta> responde en ~2 ms cuál es el hash VIGENTE sin construir; --check dice si ya está sellado. ArtifactHash::of_tree es la otra cosa: el hash del contenido real de un árbol, usado para verificar reproducibilidad y para el mirror.
  • Corolario peligroso: strip posterior al sellado rompería la verificación bit-a-bit, porque el hash de entrada seguiría apuntando a algo que un rebuild ya no reproduce.

3.3 LabFingerprint — el toolchain como entrada (2026-08-10)

Se computa del apk db del rootfs real que va a compilar (no de un fichero declarado). Entran compiladores/enlazadores (gcc, clang, llvm, binutils, rust, cargo), las libs de codegen de gcc (gmp, mpfr, mpc, isl), el runtime que se enlaza dentro (musl, libgcc, libstdc++, libatomic, libgomp) y los headers que se compilan dentro (linux-headers, fortify-headers). Quedan fuera a propósito autotools y shells: es una decisión de coste, no una afirmación de que no influyen.

Por qué existe: reconstruyendo los cuatro kernels en un segundo hub, el .config instalado salió distinto en 4 líneas (CONFIG_RUSTC_VERSION 109600→109700) — Rust 1.96 vs 1.97 del rootfs, y Rust ni siquiera estaba activado en ese kernel. Antes de esto, dos labs sellaban bytes distintos en la MISMA dirección y el store no podía notarlo.

3.4 Store, hidratación, overlay, diario

  • Store: inmutable, append-only, deduplicado, GC por alcanzabilidad (scripts/store-gc.sh distingue SUPERADO —hay ejemplar mejor— de HUÉRFANO —ejemplar único—). Store::has rechaza directorios vacíos (un vacío es un nombre, no un artefacto).
  • Hidratación (ADR 0005): hardlink directo en el caso estático; en el dinámico se copia y se reescribe con patchelf --set-interpreter/--set-rpath (no se hardlinkea, o patchelf mutaría el inode del store). Escribir encima rompe el hardlink ⇒ el store queda intacto como base de rollback.
  • Overlay (SDD 04): overlayfs con upperdir mutable sobre el FHS real. try/commit/ discard/status. El apilamiento es total y determinista (created_at + id + seq) y commit/discard exigen LIFO (Error::Shadowed si una capa más joven sombrea el target).
  • Diario (SDD 05): fanotify (FAN_CLOSE_WRITE) sobre /bin,/sbin,/usr/bin,/usr/sbin, /lib,/usr/lib,/etc; JSON-líneas append-only; op ∈ {Create, Edit, Delete} derivado por classify_op; content_hash tras la mutación con de-dup idempotente. Distingue mutación trazable (vino de un artefacto conocido) de opaca (pisada a mano) sin prohibir ninguna.

3.5 .swm — la unidad de intercambio (SDD 06)

YAML con base (distro_version + pins), mutations y signature opcional (Ed25519). Cuatro tipos de mutación: source_patch (una Recipe serializada para viajar), config_edit (hunks exactos, sin fuzz), init_rule y file_drop (content_b64 XOR content_url, siempre con content_hash BLAKE3 verificado antes de escribir nada).

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
fetch No (red) Clona al commit / baja el tarball y verifica sha256; vendorea Cargo/Go
patch Aplica source.patches
configure Heurística de build system: autotools / cmake / meson / cargo / make plano
compile Compila al target/link de la receta
install make install DESTDIR=/out o equivalente
seal No Hashea /out y lo deposita en el store

Materialización de build-deps: cada deps.build se construye recursivamente y su artefacto se apila como capa --overlay-src BAJO el rootfs del sandbox, dejando sus usr/{include,lib, lib/pkgconfig} en /usr. Así pkgconf y zig cc los encuentran sin plumbing de flags, y las recetas sin deps quedan byte-iguales (baseline intacto).

4.3 Poliglotismo

  • C/C++: zig cc por defecto (ADR 0003: un binario = compilador + musl + headers, hermético, cross). Escotilla compiler = "gcc" para gcc-ismos (hoy ~16 recetas).
  • Cargo: triple traducido (x86_64-linux-muslx86_64-unknown-linux-musl), wrapper zig cc como CARGO_TARGET_<triple>_LINKER (cargo no acepta linker de dos palabras), --release --locked --offline con vendor/ obligatorio. link = "dynamic" añade -C target-feature=-crt-static (necesario para dlopen de Vulkan/Wayland).
  • Go: 362 recetas (medido); el go mod vendor ocurre en el host, antes del sandbox.

4.4 harkaq — la jaula (SDD 16)

Tesis: bwrap da hermeticidad respecto del host, pero monta un rootfs Alpine entero como capa base ⇒ política ⊋ clausura: una receta puede usar cualquier header o binario de Alpine sin declararlo y el build pasa en verde. harkaq cierra esa brecha: la clausura de deps declaradas ES la política (Landlock + seccomp), y el producto es evidencia negativa: hermético ⟺ política = clausura ∧ denegaciones = ∅.

Decisiones cerradas: D1 política derivada, no escrita · D2 evidencia negativa (por eso es barata) · D3 cero quieting (las denegaciones SON el producto; ABI 10 trae ADD_RULE_QUIET y se decide deliberadamente no usarlo) · D4 seccomp obligatorio, no opcional · D7 fallo cerrado y honesto sobre el ABI (kernel sin Landlock ⇒ build marcado sin evidencia, nunca fingir) · D9 el canario deliberado: denials=[] no se cree, se gana (se antepone un acceso que DEBE ser denegado; si no aparece, el lector está roto).

Estado: Fase 0 y Fase 1 cerradas (zlib real: configure→Hermetico, compile→Impuro por /usr/bin/make no declarado); wiring en 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

  • /run/init.control — FIFO de control humano, texto crudo (echo "restart network" > …).
  • /run/agent.sock — socket de agente: JSON-líneas, handshake Hello/Welcome, auth por SO_PEERCRED, capacidades leídas de /etc/hammer/agent-caps.toml (default + [[rule]] por uid/gid, primera que casa gana).

Comandos: Compile, Inject, Query, Init. Eventos: BuildReady, BuildFailed (con log_tail real de 50 líneas del compilador), Injected, QueryResult, InitAck, Modified, Crashed, Error.

Capacidades mínimas: por defecto un agente sólo obtiene query. compile, inject (a overlay) e inject-real (al FHS) se conceden por separado; inject-real exige confirmación humana por política.

5.2 El bucle

intención NL → PLAN (.swm) → BUILD (lab) → TRY (overlay) → VERIFY (tests + evidence)
             → PROPOSE (diff + evidencia) → HUMANO decide commit/discard

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:

  • Stage 1 bootea en QEMU con arje-zero como PID 1 (init propio, del monorepo hermano tawasuyu) leyendo su seed card /ente/seed.card.json y supervisando hammerd + getty. El CRASHED real que la Fase 5 había diferido está demostrado: kill hammerd → arje detecta Killed(SIGTERM), aplica backoff y re-encarna.
  • Stage 2 ✓ REPRODUCIBLE host↔VM: of_tree(stage1') == of_tree(stage1).
  • Auto-alojamiento puro (variante b) CERRADO: el toolchain del builder lo construye takana desde fuente. Los 6 swaps (make, busybox, coreutils, bwrap, linux-headers, rust) corrieron juntos in-VM dando ✓ REPRODUCIBLE (of_tree = 7fa6cb4e…). La cadena de Rust es purista: mrustc → rustc 1.90.0 → 1.91.0 → 1.91.1 con x.py real, reusando un LLVM 20.1.8 externo.
  • Kernel from-source CERRADO: Linux 6.16.12 construido por takana bootea la VM del selfhost-verify y reproduce bit a bit. flex, bison, openssl, elfutils (sólo libelf, para objtool) de-Alpinizados como build-deps del kernel.

Los dos no-determinismos que hubo que cazar (valen como lección general):

  1. codegen-units=1 NO bastaba: el backend paralelo de rustc/LLVM (ThinLTO) divergía ~62 KB en arje-zero a >1 CPU. Fix: CARGO_BUILD_JOBS=1 serializa el jobserver ⇒ reproducible e independiente del nº de CPUs.
  2. El baseline "bueno" anterior (0039b2b9…) era un build paralelo no fiable. Dos números iguales de un fallo son señal, no coincidencia; y un baseline no verificado es una foto vieja con aspecto de dato fresco.

8. Release engineering (SDD 13) — imágenes, instalador, mirror, upgrades

  • E1 imagen auto-booteable (GRUB BIOS i386-pc, instalado en userspace sin root con grub-mkimage + grub-bios-setup sobre el fichero imagen). Layout GPT: BIOS-boot / / / /store / /var/lib/hammer.
  • E2 instalador a disco físico: takana-install <dev> corre dentro del live como root real (MBR, ext2 montable como ext4, GRUB por dos dd). Autoinstalador: copia la propia raíz del live.
  • E3 mirror del store (takana mirror push|pull|status): CAS replicado; el receptor recomputa of_tree y exige que case antes de sellar ⇒ una transferencia corrupta se rechaza.
  • E4 upgrades con generaciones: apply in-place fichero-a-fichero (escritura a temporal + rename), current como commit-point, backup/ con los bytes previos, rollback exacto, prune, y pending.json escrito ANTES de proyectar para que recover complete (roll-forward) o deshaga (roll-back). hammer-recover (binario estático) lo hace al arranque, antes de encarnar a arje-zero, y nunca aborta el boot.
  • E5 ISO/medio live: híbrido BIOS+UEFI (dos El Torito), initramfs = product-rootfs cpio.gz, todo desde RAM. Dogfooding: el host no traía xorriso ni mtools ⇒ los construye takana.
  • Arranque soberano EFI-stub sin GRUB (ADR 0010), con cero parpadeo validado por captura de framebuffer.

9. Arranque por grafo (ADR 0010) — la frontera con tawasuyu/mirada

El menú de arranque no es una lista de kernels: es la navegación del grafo de estados del sistema. 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)

Frente Estado Notas clave
mirada perfil 33/33 (medido) Compositor Wayland propio (tawasuyu) + greeter, sobre mesa software o iris
KDE Plasma 6 perfil escritorio-kde 163/163 (medido) Hidratado rebuild-free vía takana hydrate <hash> --into (install-reproduce no escala). ADR 0011
GNOME perfil 124/127, 3 aparcadas por diseño (medido) Keystone: el shell es una isla dinámica — gjs dlopenea .so reales ⇒ la introspección los exige. mutter→gnome-shell es HUB-ONLY estructural
COSMIC perfil 89/90 (medido) 4º escritorio usable, 6 apps + panel/dock, portal cerrado (captura da PNG real). Barato: no hay torre de C debajo
wlr/sway perfil escritorio-sway 129/129 (medido), arranca en QEMU 10 recetas en una noche: wlroots, sway, yambar, fuzzel, swaybg/lock/idle, grim, slurp, wl-clipboard

Stack gráfico (SDD 14): Mesa 24.0.9 iris-only, sin LLVM y sin Vulkan. El pin es duro: desde 24.1 iris exige intel-clc ⇒ libclc + clang/LLVM. Debajo: libdrm, wayland (+ el parche wayland-scanner-dup2-output — bajo musl dinámico freopen(@OUTPUT@) trunca el header por la copy-relocation de stdout), wayland-protocols, seatd, libinput/libevdev/mtdev/libudev-zero, libxkbcommon, pixman.

La lección transversal de este bloque, y probablemente la más importante del proyecto:

Sellado ≠ arranca. El perfil escritorio-sway cerró 121/121, falta 0 — y entre eso y una pantalla con algo dibujado hubo seis muros y ocho ciclos de imagen: PATH vacío, libz.so.1 ausente del rootfs, un backend de libseat que nuestra receta no construye, /run de sólo lectura, los datos XKB ausentes, y falta de tipografías. Ninguno era una dependencia de build, así que ninguno podía aparecer en el grafo. Y el método: el veredicto es contar colores del PPM, no leer el log — hubo un arranque con todo verde en el log y la captura 100 % negra.


12. Infraestructura operativa (lo que no está en los SDD pero gobierna el día a día)

12.1 Topología

  • Hub principal: laptop del autor + un segundo hub gioser.net (server Hetzner protegido, sin backup, mismo proyecto hcloud que la granja). El store vive en un volumen dedicado (bind-mount de store, work/sources, work/repos, CARGO_HOME).
  • Granja efímera: workers hcloud on-demand (farm-up / farm-run / farm-down, baseline €0), arrancados desde un snapshot golden que trae el toolchain + el store horneados ⇒ cache-hit instantáneo. El worker es compute puro y sin secretos (ni firma ni gitea). Lleva un dead-man que lo borra (no lo apaga — apagado seguiría facturando) tras 1 h idle.
  • Latido: un cron cada 30 min cosecha el worker, siembra recetas y regenera el grafo. Los commits estado: cosecha granja <ts> del historial son ese latido.
  • Respaldo: Storage Box BX11 (1 TiB), interrumpible y continuable; sube primero el cerebro (estado + repo) y después el store.
  • Espejo: origin empuja a gitea y a un espejo privado de GitHub.

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

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

  • docs/state/build-state*.json = el KHIPU: el registro firme y versionado (nudos = recetas, cuerdas = deps). Fuente única de verdad de QUÉ hay y QUÉ falta. Se regenera (scripts/build-state.py); un generado que no se regenera es una foto vieja con aspecto de dato fresco (error real: KDE reportaba 162/162 cuando estaba en 76/162).
  • scripts/yupana.py = la YUPANA: el motor que reckona sobre el khipu. Es la puerta única de la metodología. Verbos: radio <receta> (¿a quién rompo si toco esto?), perfiles, duplicados, keystones, preflight, estado, objetivo, frontera, triaje, drenar.

La regla que la justifica: qué nodos puntúo es una decisión de vista; quién me consume es un hecho y nunca depende de la vista. Nació de un fallo real: un cambio a libdrm invalidó 118 recetas KDE, y el radio medido sólo contra recipes/*.toml dio 8. Las dependencias inversas se calculan SIEMPRE sobre todas las colas.

Resolución sibling-first: una dep libdrm desde incoming-kde resuelve a incoming-kde/libdrm si existe, y si no cae a corpus/libdrm — igual que el sandbox al apilar capas. Por eso los nodos se identifican por par (cola, nombre), no por nombre.

Otros órganos: targets.py (perfiles → lista), drenar.py (orden de masticado en ondas), triaje.py, seed-graph.py --frontera, affected.py (CVE por grafo), why-differs.py, verificar-repro.sh, store-gc.sh, licencias*.sh, fuentes/mirror-* (ADR 0013).

12.3 targets.toml — el manifiesto de OBJETIVO

build-state.json es el grafo de lo que EXISTE; targets.toml es lo que la distro debe tener. La inversión clave: paquetes lista sólo las raíces (lo que un usuario pide por nombre); la clausura la calcula el grafo. Siete perfiles: base, cli, escritorio-mirada, escritorio-kde, escritorio-gnome, escritorio-cosmic, escritorio-sway. Un perfil = una imagen enviable; hereda compone; cola dice en qué árbol viven sus raíces.

12.4 ADR 0013 — mirror de fuentes (obligación GPL, y sale barato)

La URL no entra en hash_inputs; sólo el sha256/commit ⇒ los mirrors son gratis. Cerrado: tarballs por sha256 (1,7 G) + git por bundle shallow (570/571, 2,3 G). Detalles pagados: 4 pines no son commits sino objetos tag (hay que pelar con ^{commit}); github.com es el 64 % del corpus. Regla dura: nunca cambiar el sha256 para tapar un 404. Vigía activo: fuentes-vigia.json1172 fuentes, 1172 vivas, 0 muertas (medido).


13. Estado real medido (2026-08-28)

13.1 Corpus

Métrica Valor (medido)
Recetas .toml activas 1172 (+25 aparcadas en */.deferred)
— corpus (recipes/) 787
incoming-kde / -gnome / -cosmic / -gnome-onda2 / -wlr / -onda1 205 / 90 / 47 / 22 / 15 / 6
Parches .patch en el árbol de recetas 123
Recetas con campo license 1096 / 1197 (92 %)
Recetas con compiler = "gcc" 36 (16 en el corpus, 20 en las colas)
Recetas con strip_debug (campaña SDD 23) 35, todas en el corpus
Recetas que pinean zig_version = "0.13.0" 84
Entradas en el store local 1654 (55 G en este disco; el store real vive en el volumen)
Fuentes vivas (vigía) 1172 / 1172, 0 muertas

13.2 Grafo de estado, por vista

Vista nodos sellados deuda
corpus 787 711 75
KDE (--kde) 978 899 74 (+4 wanted)
GNOME 861 783 74 (+4 never)
COSMIC 833 757 75
wlr 801 726 74

Deuda del corpus por clase: go 37 · rust 26 · c 12 · gui 1.

Perfiles (medido): base 52/52 · cli 75/75 · escritorio-mirada 33/33 · escritorio-kde 163/163 · escritorio-sway 129/129 · escritorio-cosmic 89/90 · escritorio-gnome 124/127.

13.3 Lectura de la deuda actual (importante para no malinterpretar los SDD 19/20)

Los SDD 19/20 (2026-08-07) hablan de «12 recetas en deuda». Hoy el corpus marca 75, y no es una regresión de calidad: la lista de nombres en deuda va alfabéticamente desde llimphi-counter/mise hasta zola — es la cola pendiente de la campaña de reconstrucción del corpus en serie (scripts/reconstruir-corpus.sh, reanudable, alfabética), disparada por los dos eventos que movieron el ArtifactHash de todo el catálogo a la vez:

  1. El lab entró en hash_inputs (2026-08-10, §3.3).
  2. El split de debug (SDD 23, strip_debug), que cambia la fase install.

Y el diagnóstico de «las 12» originales fue desmontado después: 6 estaban SUPERADAS por la cadena dinámica de GNOME, 1 cerrada (wlr-randr: le faltaban python3 —meson es un script de Python— y libffi, que exige wayland-client.pc en su Requires), 1 es frente propio (elfutils-libdw, ya cerrado: dwarves construye) y 3 son HUB-ONLY estructural (fuente por SSH a gitea privado; el worker es sin secretos por diseño). El hallazgo de fondo fue de encolado, no técnico: recipes/ (el corpus) no estaba en la lista QUEUES del bucle del worker — nadie las estaba intentando.


14. Deuda abierta y problemas sin resolver

14.1 ADR 0012 — la carrera del árbol de fuentes (PENDIENTE, sin decidir)

fetch nombra el árbol de forma determinista y sin componente por-constructor: work/sources/<nombre>-<sha16>. Dos builds de la misma dep resuelven al MISMO directorio; uno hace remove_dir_all mientras el otro corre tar -x y el árbol queda roto para siempre.

El árbol cumple dos papeles que se contradicen bajo concurrencia: caché direccionada por contenido y workspace mutable de build (patches, cargo vendor, go mod vendor — minutos y decenas de GB). Por eso un lock sólo en la extracción parece correcto y NO lo es: un segundo proceso puede borrar un árbol que un bwrap está usando para compilar.

Medido a escala: al invalidar libdrm, ~93 de 205 recetas KDE murieron con /src/.zwrap/cc is not a full path to an existing compiler tool — el wrapper de zig que la receta deja EN EL ÁRBOL, barrido por el fetch concurrente de otra receta.

Mitigación vigente (no solución): flock work/.farm-build.lock alrededor de todo 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

  • 36 recetas con compiler = "gcc" (medido: 16 en el corpus + 20 en las colas de escritorio; contadas parseando TOML, porque grep cuenta comentarios). Las 12 de Rust son un solo problema: falta el unwinder de libgcc (perilla LIBS=-lunwind ya identificada).
  • Techo MSRV del sandbox = 1.96. La frontera son los *-sys con C++ — y es exactamente lo que bloquea Firefox. Antes del navegador hay que subir el techo.
  • *-sys que piden libs C sin [deps]: unable to find static system library 'lzma'/'bz2' en recetas Cargo. Arreglo por receta (xz, bzip2, sqlite), nunca engordar el rootfs, y nunca en bloque: hay ~220 recetas Cargo sin deps y re-hashearlas invalidaría las selladas.
  • Multi-init: los inits del catálogo (openrc, etc.) compiten con arje-zero, no se apilan.
  • link=static es a veces mentira: cuatro causas distintas (libtool se come el -static; el Makefile lo pisa; link es INERTE en Go; la receta Cargo pisa el lab y pierde crt-static). Auditado: 0/680 mienten hoy, pero el audit sobre-reporta si no se re-sella antes.
  • Licencias: 1096/1197 declaran; faltan ~101 y hay que desambiguar SPDX obsoletos (GPL-3.0 no dice si -only u -or-later). Regla dura: no se rellena a ojo — declarar mal es peor que dejar vacío, porque convierte un hueco visible en una afirmación falsa. El cierre definitivo es capturar la licencia en la fase de fetch, donde el dato pasa gratis.
  • Falta para publicar (SDD 19/20/21): espejo de fuentes servido, clave raíz fuera de línea (lo único no delegable a un agente), imagen validada en metal con pantalla, upgrade N→N+1 con rollback probado, cups y bluez, documentación mínima y canal de reportes.
  • Aplicaciones gráficas de terceros: cero. Barato: imv, zathura, mpv, gestor de archivos. Caro: LibreOffice. El más caro del catálogo: un navegador.

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

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

  • H1 — Proof-carrying recipes : el .swm lleva un bloque evidence ({kind, cmd, expected}, kind ∈ {cmd-exit, proptest, contract, kani}); takana swm-verify --evidence corre cada ítem dentro del sandbox reproducible. La evidencia corre del lado del BUILD (separación PROPONE/CONSTRUYE: takana-agent no depende de takana-build); Event::BuildReady lleva el EvidenceVerdict y el Orchestrator aborta antes de hidratar si falla. Frontera honesta declarada: garantiza que lo declarado pasa, no que lo declarado basta.
  • H2 — Memoización de ejecución (en wawa-memo, repo hermano): si el wasm es determinista, blake3(módulo) + blake3(entrada) + E → blake3(salida) es una función pura y su caché es un LwwMap monótono compartible por anti-entropy. E es un hash-de-entorno que pinea wasmi/features/fuel/mem/ABI. Único vector residual acotado: payload de NaN vía reinterpret.
  • H3 — Código por contenido estilo Unison (subconjunto): Base content-addressed de funciones wasm — identidad = blake3(bytecode), nombres como metadata separada, actualizar no rompe (v1 y v2 coexisten), composición por hash. ADR 0009 decide NO reescribir la unidad de compilación; el puente barato es procedencia por-símbolo como metadata.
  • H4 — Configuraciones compartidas : el eje nuevo son los slots. Una modificación reclama (claim: slot→Id) y requiere (requires: slot→Id). Compatible queda definido duro: requisitos resueltos + reclamos disjuntos. Dos casos que el modelo trata distinto: logo (dos configs escriben la misma superficie ⇒ colisión = elección) y wayland (dependencia que no resuelve ⇒ rechazo). Subido a la receta real ([slots], fuera de hash_inputs) y a la InstalledDb (system_state() → slot → hash). CLI: takana compat.

La ambición declarada al final del SDD 15: config = paquete = función = proceso, todos el mismo tipo de objeto direccionado por el mismo hash.

15.2 SDD 17 — los diez cierres de frontera, priorizados

Tesis: los tres invariantes ya pagados —bit-reproducibilidad, direccionamiento por contenido y clausura-como-política— hacen casi gratis para 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 grafotakana 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 (KBMB, cero datos) → CAS de objetos → fs-verity opcional → ext4.

Propiedades que caen solas: dedup de disco y de page cache, activar/rollback O(metadata), inmutabilidad estructural, y bit-repro en runtime (el kernel verifica el Merkle en cada lectura; un bit podrido = EIO, no un binario silenciosamente distinto).

Identidad: dos granos, un mapa. blake3 sigue siendo la identidad (recetas, sellado, grafo); el digest fs-verity (sha256) es el enforcement. Misma jugada que ADR 0009.

ADR 0005 (hardlinks) sigue vigente para el laptop de desarrollo; wawafs es el modo despliegue (ro). El corte cae donde el workload cambia de naturaleza: se escribe una vez al sellar, se lee mil veces al vivir. Prototipo medido (2026-07-17): hydrate 28 ms vs mkcomposefs 507 ms sobre un rootfs de 262 MB; manifiesto .cfs de 136 KB firmables. Hallazgo: con hardlinks, un write con privilegios a través del árbol hidratado corrompe el store (comparten inode); con composefs es imposible por construcción. Precondición: el kernel propio trae OVERLAY_FS+REDIRECT_DIR+METACOPY pero falta EROFS_FS y FS_VERITY ⇒ re-sellado de kernel.


16. Los ADR, con su estado

# Decisión Estado
0001 Rust para el tooling y los daemons aceptada
0002 Validar sobre Alpine antes de la distro propia aceptada
0003 zig cc como compilador por defecto del lab aceptada
0004 No escribir nuestro propio Nix aceptada
0005 Hidratación por hardlinks aceptada (convive con wawafs como backend futuro)
0006 Commits fijados, no HEAD vivo aceptada
0007 arje como init propio del track posterior propuesta (implementada de facto)
0008 Bootstrap en 3 stages, zig como semilla aceptada
0009 Código direccionado por contenido (Unison): registrar la visión, no reescribir propuesta / design-doc
0010 Arranque por grafo: el menú de boot como navegación del grafo aceptado; lado takana 15
0011 Etapa Escritorio: campaña KDE Plasma 6 aceptado; gates H-Qt y H-QML pasados
0012 El árbol de fuentes es caché y workspace a la vez PENDIENTE, sin decidir
0013 Mirror de fuentes: la URL es transporte, el sha256 es la identidad ACEPTADO, implementado

17. Superficie del CLI (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érmino Qué es
el lab / el sótano El laboratorio de build hermético
sellar Depositar un artefacto en el store bajo su hash
hidratar Proyectar un artefacto del store al FHS
la granja La flota efímera de workers hcloud
el hub La máquina con secretos (laptop o gioser); firma, gitea, clasifica
el latido El cron */30 que cosecha, siembra y regenera el grafo
cola / queue Un árbol de recetas: corpus = recipes/; el resto recipes/incoming-<x>/
sombra Una receta homónima en otra cola que gana por sibling-first
deuda Nodo del grafo sin artefacto en su hash vigente
drenar Construir la deuda en ondas topológicas
frontera Lo que upstream pide y no tenemos
keystone Nodo en deuda que gatea el máximo trabajo bloqueado
radio Cuántos nodos rompo si toco una receta
superado / huérfano Artefacto con ejemplar mejor (podable gratis) / ejemplar único
de-Alpinizar Quitarle a una receta la dependencia implícita del rootfs Alpine
harkaq La jaula (Landlock+seccomp) que verifica que lo declarado basta
yupana / khipu El motor de cálculo sobre el estado / el registro firme del estado
arje / arje-zero El init propio (PID 1) del monorepo hermano tawasuyu
mirada El compositor Wayland de tawasuyu que dibuja el menú de arranque
wawa / wawafs El runtime wasm de tawasuyu / el frente de store-como-FS

20. Dónde hay hueco para SDDs nuevos

Esto es lectura del autor de este informe, no doctrina del repo. Los huecos donde un SDD tendría tracción real, con la razón por la que hoy no existe uno:

  1. ADR 0012 resuelto → SDD del modelo de árbol de fuentes. Es la única decisión estructural marcada PENDIENTE, bloquea el paralelismo del worker (JOBS=1) y ya rompió una campaña entera. Cualquier propuesta debe cubrir extracción + build, no sólo la extracción, y decir qué pasa con cargo vendor (minutos, decenas de GB) y con el diamante que converge en qtbase.
  2. SDD del split de debug terminado (SDD 23 está a medias): qué se hace con el paquete -debug en sí, cómo viaja por el repo .swm, y si -ffile-prefix-map entra o no. Decide ~96 G de disco y el tamaño del espejo público.
  3. SDD de gestión de claves y cadena de release. Hoy hay firma de índice; falta clave raíz offline, rotación, procedimiento de compromiso. Es bloqueante para publicar y el propio SDD 21 lo marca como lo único no delegable a un agente.
  4. SDD de política de runtime (cierre §3 de SDD 17): el diseño de dos niveles ya está medido —frontera como bits de un u32 firmado, detalle medido corriendo el binario— pero no hay documento que lo fije ni herramienta que lo emita end-to-end.
  5. SDD de consenso de reconstrucción (cierre §1 de SDD 17): el más diferenciador y el que convierte la reproducibilidad de QA en primitiva de distribución. Las piezas (granja, bootstrap.json, fork-proof, umbral) existen; falta el protocolo.
  6. SDD de validación de imagen: de «cierra en el grafo» a «arranca». La lección sellado ≠ arranca costó seis muros y ocho ciclos y no tiene documento: hoy vive como anécdota en el §II.1 del SDD 20. Un harness reproducible (contar colores del PPM, comparar NEEDED del ELF contra el rootfs, verificar datos XKB/tipografías//run rw) evitaría pagarlo una vez por perfil.
  7. SDD del catálogo de aplicaciones gráficas, con el techo MSRV como precondición explícita: hoy el navegador está presupuestado como frente propio pero sin plan escrito, y el orden correcto (subir el techo → *-sys con C++ → Firefox) sólo aparece como nota en el SDD 21.
  8. SDD de wawafs F0F3 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.mddocs/00-vision.mddocs/01-architecture.mddocs/10-roadmap.mddocs/state/build-state.json (regenerándolo) → scripts/yupana.py radio <receta> antes de tocar nada compartido.