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
54 KiB
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í):
- Compilación determinista ⇒ la IA cambia fuente, recompila, y el binario es predecible.
- 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. - Mutabilidad = agencia real, con overlay como red de seguridad.
- musl mantiene el C legible para el contexto de un LLM (glibc es un laberinto de macros).
- 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_inputses una LISTA BLANCA. Lo que no está enumerado no mueve el hash. Por esolicense,evidenceyslotsse 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
-jde una fase cambia el hash aunque el binario sea idéntico; ⇒ el.configdel kernel (que vive en la faseconfigure) es la identidad del artefacto (SDD 22 §1); ⇒strip_debugobliga 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;--checkdice si ya está sellado.ArtifactHash::of_treees la otra cosa: el hash del contenido real de un árbol, usado para verificar reproducibilidad y para el mirror. - Corolario peligroso:
stripposterior 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.shdistingue SUPERADO —hay ejemplar mejor— de HUÉRFANO —ejemplar único—).Store::hasrechaza 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
upperdirmutable sobre el FHS real.try/commit/discard/status. El apilamiento es total y determinista (created_at+id+seq) ycommit/discardexigen LIFO (Error::Shadowedsi 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 porclassify_op;content_hashtras 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 |
Sí | Aplica source.patches |
configure |
Sí | Heurística de build system: autotools / cmake / meson / cargo / make plano |
compile |
Sí | Compila al target/link de la receta |
install |
Sí | make install DESTDIR=/out o equivalente |
seal |
No | Hashea /out y lo deposita en el store |
Materialización de build-deps: cada deps.build se construye recursivamente y su artefacto se
apila como capa --overlay-src BAJO el rootfs del sandbox, dejando sus usr/{include,lib, lib/pkgconfig} en /usr. Así pkgconf y zig cc los encuentran sin plumbing de flags, y las recetas
sin deps quedan byte-iguales (baseline intacto).
4.3 Poliglotismo
- C/C++:
zig ccpor defecto (ADR 0003: un binario = compilador + musl + headers, hermético, cross). Escotillacompiler = "gcc"para gcc-ismos (hoy ~16 recetas). - Cargo: triple traducido (
x86_64-linux-musl→x86_64-unknown-linux-musl), wrapperzig cccomoCARGO_TARGET_<triple>_LINKER(cargo no acepta linker de dos palabras),--release --locked --offlineconvendor/obligatorio.link = "dynamic"añade-C target-feature=-crt-static(necesario paradlopende Vulkan/Wayland). - Go: 362 recetas (medido); el
go mod vendorocurre 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, handshakeHello/Welcome, auth porSO_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.jsony supervisando hammerd + getty. ElCRASHEDreal que la Fase 5 había diferido está demostrado:kill hammerd→ arje detectaKilled(SIGTERM), aplica backoff y re-encarna. - Stage 2
✓ REPRODUCIBLEhost↔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.1conx.pyreal, 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):
codegen-units=1NO bastaba: el backend paralelo de rustc/LLVM (ThinLTO) divergía ~62 KB enarje-zeroa >1 CPU. Fix:CARGO_BUILD_JOBS=1serializa el jobserver ⇒ reproducible e independiente del nº de CPUs.- 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-setupsobre 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 dosdd). Autoinstalador: copia la propia raíz del live. - E3 mirror del store (
takana mirror push|pull|status): CAS replicado; el receptor recomputaof_treey 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),currentcomo commit-point,backup/con los bytes previos, rollback exacto,prune, ypending.jsonescrito ANTES de proyectar para querecovercomplete (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íaxorrisonimtools⇒ 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:
- 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 unArtifactHash+ 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, ykernel plandebe emitir una receta derivada. - La clausura por
depends onNO cierra:selectes el portillo. Kconfig tiene al menos tres tipos de arista (depends onhacia arriba,selecthacia abajo y forzada,implyblanda) con semánticas incompatibles. Hay corpus de verdad-de-campo contra el que validar: elconfiguredelinux.tomlya 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-swaycerró 121/121, falta 0 — y entre eso y una pantalla con algo dibujado hubo seis muros y ocho ciclos de imagen:PATHvacío,libz.so.1ausente del rootfs, un backend de libseat que nuestra receta no construye,/runde 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 destore,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:
originempuja a gitea y a un espejo privado de GitHub.
12.2 El khipu y la yupana — la metodología de medición
Distinción explícita y arquitectónica, no adorno:
docs/state/build-state*.json= el KHIPU: el registro firme y versionado (nudos = recetas, cuerdas = deps). Fuente única de verdad de QUÉ hay y QUÉ falta. Se regenera (scripts/build-state.py); un generado que no se regenera es una foto vieja con aspecto de dato fresco (error real: KDE reportaba 162/162 cuando estaba en 76/162).scripts/yupana.py= la YUPANA: el motor que reckona sobre el khipu. Es la puerta única de la metodología. Verbos:radio <receta>(¿a quién rompo si toco esto?),perfiles,duplicados,keystones,preflight,estado,objetivo,frontera,triaje,drenar.
La regla que la justifica: qué nodos puntúo es una decisión de vista; quién me consume es un
hecho y nunca depende de la vista. Nació de un fallo real: un cambio a libdrm invalidó 118
recetas KDE, y el radio medido sólo contra recipes/*.toml dio 8. Las dependencias inversas se
calculan SIEMPRE sobre todas las colas.
Resolución sibling-first: una dep libdrm desde incoming-kde resuelve a incoming-kde/libdrm
si existe, y si no cae a corpus/libdrm — igual que el sandbox al apilar capas. Por eso los nodos
se identifican por par (cola, nombre), no por nombre.
Otros órganos: targets.py (perfiles → lista), drenar.py (orden de masticado en ondas),
triaje.py, seed-graph.py --frontera, affected.py (CVE por grafo), why-differs.py,
verificar-repro.sh, store-gc.sh, licencias*.sh, fuentes/mirror-* (ADR 0013).
12.3 targets.toml — el manifiesto de OBJETIVO
build-state.json es el grafo de lo que EXISTE; targets.toml es lo que la distro debe tener.
La inversión clave: paquetes lista sólo las raíces (lo que un usuario pide por nombre); la
clausura la calcula el grafo. Siete perfiles: base, cli, escritorio-mirada, escritorio-kde,
escritorio-gnome, escritorio-cosmic, escritorio-sway. Un perfil = una imagen enviable;
hereda compone; cola dice en qué árbol viven sus raíces.
12.4 ADR 0013 — mirror de fuentes (obligación GPL, y sale barato)
La URL no entra en hash_inputs; sólo el sha256/commit ⇒ los mirrors son gratis. Cerrado:
tarballs por sha256 (1,7 G) + git por bundle shallow (570/571, 2,3 G). Detalles pagados: 4 pines no
son commits sino objetos tag (hay que pelar con ^{commit}); github.com es el 64 % del
corpus. Regla dura: nunca cambiar el sha256 para tapar un 404. Vigía activo:
fuentes-vigia.json → 1172 fuentes, 1172 vivas, 0 muertas (medido).
13. Estado real medido (2026-08-28)
13.1 Corpus
| Métrica | Valor (medido) |
|---|---|
Recetas .toml activas |
1172 (+25 aparcadas en */.deferred) |
— corpus (recipes/) |
787 |
— incoming-kde / -gnome / -cosmic / -gnome-onda2 / -wlr / -onda1 |
205 / 90 / 47 / 22 / 15 / 6 |
Parches .patch en el árbol de recetas |
123 |
Recetas con campo license |
1096 / 1197 (92 %) |
Recetas con compiler = "gcc" |
36 (16 en el corpus, 20 en las colas) |
Recetas con strip_debug (campaña SDD 23) |
35, todas en el corpus |
Recetas que pinean zig_version = "0.13.0" |
84 |
| Entradas en el store local | 1654 (55 G en este disco; el store real vive en el volumen) |
| Fuentes vivas (vigía) | 1172 / 1172, 0 muertas |
13.2 Grafo de estado, por vista
| Vista | nodos | sellados | deuda |
|---|---|---|---|
| corpus | 787 | 711 | 75 |
KDE (--kde) |
978 | 899 | 74 (+4 wanted) |
| GNOME | 861 | 783 | 74 (+4 never) |
| COSMIC | 833 | 757 | 75 |
| wlr | 801 | 726 | 74 |
Deuda del corpus por clase: go 37 · rust 26 · c 12 · gui 1.
Perfiles (medido): base 52/52 · cli 75/75 · escritorio-mirada 33/33 ·
escritorio-kde 163/163 · escritorio-sway 129/129 · escritorio-cosmic 89/90 ·
escritorio-gnome 124/127.
13.3 Lectura de la deuda actual (importante para no malinterpretar los SDD 19/20)
Los SDD 19/20 (2026-08-07) hablan de «12 recetas en deuda». Hoy el corpus marca 75, y no
es una regresión de calidad: la lista de nombres en deuda va alfabéticamente desde
llimphi-counter/mise hasta zola — es la cola pendiente de la campaña de reconstrucción del
corpus en serie (scripts/reconstruir-corpus.sh, reanudable, alfabética), disparada por los dos
eventos que movieron el ArtifactHash de todo el catálogo a la vez:
- El lab entró en
hash_inputs(2026-08-10, §3.3). - El split de debug (SDD 23,
strip_debug), que cambia la faseinstall.
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, porquegrepcuenta comentarios). Las 12 de Rust son un solo problema: falta el unwinder de libgcc (perillaLIBS=-lunwindya identificada). - Techo MSRV del sandbox = 1.96. La frontera son los
*-syscon C++ — y es exactamente lo que bloquea Firefox. Antes del navegador hay que subir el techo. *-sysque 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=statices a veces mentira: cuatro causas distintas (libtool se come el-static; el Makefile lo pisa;linkes INERTE en Go; la receta Cargo pisa el lab y pierdecrt-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.0no dice si-onlyu-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,
cupsybluez, 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)
- Un artefacto VACÍO es un cache-hit.
takana buildmiraba 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. - El cache-hit congela regresiones. «No re-hashea nada sellado» ≠ «es seguro»: una regla del
wrapper llevaba meses rompiendo
makesin 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. - 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.
grepno es una medición en este repo. Contó comentarios en el conteo de licencias (grep -l license→ 5 falsos positivos), contóIO_URINGen un comentario del kernel, y contó.rela.debug_*en la regla del.ano-PIC (readelf -r | grep R_X86_64_32miente si no se excluye debug). El veredicto se obtiene reconstruyendo y comparando, no buscando cadenas..dmergeretiene lo que borrás del store (hardlinkea los artefactos) ⇒ podar el store no libera disco mientras la caché los referencie. Medir const_nlink==1, nunca condu(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
.swmlleva un bloqueevidence({kind, cmd, expected},kind ∈ {cmd-exit, proptest, contract, kani});takana swm-verify --evidencecorre cada ítem dentro del sandbox reproducible. La evidencia corre del lado del BUILD (separación PROPONE/CONSTRUYE:takana-agentno depende detakana-build);Event::BuildReadylleva elEvidenceVerdicty elOrchestratoraborta 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 unLwwMapmonótono compartible por anti-entropy.Ees un hash-de-entorno que pinea wasmi/features/fuel/mem/ABI. Único vector residual acotado: payload de NaN víareinterpret. - H3 — Código por contenido estilo Unison ✅ (subconjunto):
Basecontent-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 dehash_inputs) y a laInstalledDb(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».
- 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.
why-differs(diffoscope propio) — implementado: nombra la causa (un MTIME en un gzip, la cabeceraarde un.a, qué sección ELF cambió, una ruta filtrada). Multiplicador de todo lo demás.- 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
htopmusl-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 (clasesdns/tls-certs/random/locale-tz/syslog, bits de unu32firmado de 36 bytes canónicos) + detalle (paths, Landlock, medidos). - CVE por grafo —
takana affected CVE-Xexacto + frontera mínima de rebuild + hydrate para repartir el parche rebuild-free. Donde Debian/Alpine aproximan por nombre-versión, takana responde exacto. - Sellar el arranque — verity + medida; el store CAS hace fs-verity casi gratis.
- 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.
- De-Alpinizar el rootfs del sandbox — canal impuro estructural mientras el sandbox se apoye en Alpine; harkaq lo mide.
takana oci— imágenes distroless bit-reproducibles desde un closure. Vector de adopción realista.- Updates diferenciales content-defined (chunking estilo casync sobre el store).
- Bucle agéntico con juez mecánico — LA tesis AI-nativa: el catálogo se cultiva solo porque
el juez es mecánico. Sin juez, las recetas de un LLM son deuda; con él, son cosecha. Ya
demostrado a mano con
zlib(Impuro→Hermetico en 3 fases, guiado sólo por harkaq).
Qué NO priorizar (dicho explícitamente): solver SAT de versiones (el grafo es curado), multi-arch (esperar demanda), config declarativa estilo NixOS (cancha ajena).
15.3 SDD 18 — wawafs: el store como filesystem del sistema vivo
Diseño aceptado, sin implementar. Bajar el checkout de userspace al kernel con la pila composefs (CAS + erofs + overlayfs + fs-verity, todo mainline): no hay que escribir un filesystem. Capas: proceso → VFS (rutas FHS reales, sin pueblo fantasma) → composefs mount → manifest erofs por clausura (KB–MB, cero datos) → CAS de objetos → fs-verity opcional → ext4.
Propiedades que caen solas: dedup de disco y de page cache, activar/rollback O(metadata), inmutabilidad estructural, y bit-repro en runtime (el kernel verifica el Merkle en cada lectura; un bit podrido = EIO, no un binario silenciosamente distinto).
Identidad: dos granos, un mapa. blake3 sigue siendo la identidad (recetas, sellado, grafo); el digest fs-verity (sha256) es el enforcement. Misma jugada que ADR 0009.
ADR 0005 (hardlinks) sigue vigente para el laptop de desarrollo; wawafs es el modo despliegue
(ro). El corte cae donde el workload cambia de naturaleza: se escribe una vez al sellar, se lee mil
veces al vivir. Prototipo medido (2026-07-17): hydrate 28 ms vs mkcomposefs 507 ms sobre un
rootfs de 262 MB; manifiesto .cfs de 136 KB firmables. Hallazgo: con hardlinks, un write con
privilegios a través del árbol hidratado corrompe el store (comparten inode); con composefs es
imposible por construcción. Precondición: el kernel propio trae OVERLAY_FS+REDIRECT_DIR+METACOPY
pero falta EROFS_FS y FS_VERITY ⇒ re-sellado de kernel.
16. Los ADR, con su estado
| # | Decisión | Estado |
|---|---|---|
| 0001 | Rust para el tooling y los daemons | aceptada |
| 0002 | Validar sobre Alpine antes de la distro propia | aceptada |
| 0003 | zig cc como compilador por defecto del lab |
aceptada |
| 0004 | No escribir nuestro propio Nix | aceptada |
| 0005 | Hidratación por hardlinks | aceptada (convive con wawafs como backend futuro) |
| 0006 | Commits fijados, no HEAD vivo |
aceptada |
| 0007 | arje como init propio del track posterior |
propuesta (implementada de facto) |
| 0008 | Bootstrap en 3 stages, zig como semilla |
aceptada |
| 0009 | Código direccionado por contenido (Unison): registrar la visión, no reescribir | propuesta / design-doc |
| 0010 | Arranque por grafo: el menú de boot como navegación del grafo | aceptado; lado takana 1–5 ✅ |
| 0011 | Etapa Escritorio: campaña KDE Plasma 6 | aceptado; gates H-Qt y H-QML pasados |
| 0012 | El árbol de fuentes es caché y workspace a la vez | PENDIENTE, sin decidir |
| 0013 | Mirror de fuentes: la URL es transporte, el sha256 es la identidad |
ACEPTADO, implementado |
17. Superficie del CLI (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
- Todo
takana buildva envuelto enflock work/.farm-build.lock. Por el ADR 0012. Para una tanda, tomar el lock una sola vez, no por receta. El lock está fuera detakana builda propósito: los scripts de granja lo toman por fuera y takana se bloquearía contra ellos. - Nunca
git add -A. Varios agentes trabajan el repo a la vez; unadd -Aarrastra ficheros a medias de otro. Commits granulares, en español, directo sobremain, ygit pushtras cada unidad de trabajo. - 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:
- 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 concargo vendor(minutos, decenas de GB) y con el diamante que converge en qtbase. - SDD del split de debug terminado (SDD 23 está a medias): qué se hace con el paquete
-debugen sí, cómo viaja por el repo.swm, y si-ffile-prefix-mapentra o no. Decide ~96 G de disco y el tamaño del espejo público. - 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.
- 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
u32firmado, detalle medido corriendo el binario— pero no hay documento que lo fije ni herramienta que lo emita end-to-end. - 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. - 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
NEEDEDdel ELF contra el rootfs, verificar datos XKB/tipografías//runrw) evitaría pagarlo una vez por perfil. - 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 →
*-syscon C++ → Firefox) sólo aparece como nota en el SDD 21. - 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.
- 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.