takana etapa 5b: los 59 docs de diseño, runbooks y ADR

645 líneas. Los ADR entran porque en este repo SON documentos vivos, no
registros inmutables: el 0013 tiene 5 commits, el 0009 dos. Eso se comprobó
antes de decidir, no se asumió por convención general.

EXCLUIDOS por ser REGISTRO o generado: docs/evidencia/ (6), el HANDOFF de la
noche de KDE (1) y docs/state/ (24, se regenera solo). Reescribir un comando
dentro de una evidencia la falsifica.

Y el ADR 0016 se excluye de todo barrido, con un aviso adentro para el próximo
que barra: habla SOBRE el renombre, así que necesita seguir diciendo 'hammer'.
El barrido se lo llevó puesto y lo dejó titulado 'Renombre del sistema: takana
→ takana'; revertido.

Congelados, verificados uno por uno con controles: /opt/hammer, /var/lib/hammer,
/usr/bin/hammer, /mnt/vvv/hammer, la URL de gitea, hammer-farm.service,
hammer-live-install.sh, BRIEFING-hammer.md, hammerd, hammer-recover y
HAMMER_LIVE.
This commit is contained in:
Sergio
2026-09-09 19:25:51 +00:00
parent e852f48491
commit 97ceb72411
56 changed files with 632 additions and 628 deletions
+2 -2
View File
@@ -53,7 +53,7 @@ kernel) es trabajo conocido. **La innovación es el substrato de baja entropía*
Mientras la industria diseña sistemas para *proteger al sistema del administrador*
(restringiendo, aislando, inmutabilizando) — y al hacerlo también ciega a las herramientas de
IA — `hammer` hace lo contrario: **mantiene las tuberías expuestas y de baja entropía**, y
IA — `takana` hace lo contrario: **mantiene las tuberías expuestas y de baja entropía**, y
trata al usuario (y a su IA) como un operador consciente, no como un peligro.
## 4. Principios de diseño
@@ -68,7 +68,7 @@ trata al usuario (y a su IA) como un operador consciente, no como un peligro.
- **El diario es la verdad.** Vives imperativamente; el sistema deriva el estado declarativo
*a posteriori*. Nunca al revés.
## 5. Anti-objetivos (lo que `hammer` NO es)
## 5. Anti-objetivos (lo que `takana` NO es)
- No es un gestor de paquetes inmutable ni un daemon que restaura estado.
- No reescribe un motor de build desde cero (ver [ADR 0004](adr/0004-no-custom-nix.md)).
+11 -11
View File
@@ -11,7 +11,7 @@
│ │ │ │ ▲ │
│ ▼ │ │ │ hardlinks normalizados │
│ ┌───────────────────────┐ │ │ │ │
│ │ hammer-build (lab) │ │ │ ┌───┴──────────────┐ │
│ │ takana-build (lab) │ │ │ ┌───┴──────────────┐ │
│ │ bubblewrap + zig cc │ compila musl │ │ │ HIDRATACIÓN │ patchelf / copy │
│ │ recetas + grafo deps │ estático │ │ └───┬──────────────┘ │
│ └──────────┬────────────┘ │ │ │ │
@@ -40,28 +40,28 @@ el host; siempre delega al lab.
| Componente | Crate | Responsabilidad única |
|---|---|---|
| **Laboratorio** | `hammer-build` | Compilar una receta de forma hermética → artefacto en el store |
| **Store CAS** | `hammer-core` | Guardar/recuperar artefactos por hash BLAKE3; garbage collection |
| **Hidratación** | `hammer-build` | Proyectar artefactos del store al FHS real (hardlink + patchelf) |
| **Overlay** | `hammer-cli` | Montar/fusionar/descartar capa de experimentación en caliente |
| **Laboratorio** | `takana-build` | Compilar una receta de forma hermética → artefacto en el store |
| **Store CAS** | `takana-core` | Guardar/recuperar artefactos por hash BLAKE3; garbage collection |
| **Hidratación** | `takana-build` | Proyectar artefactos del store al FHS real (hardlink + patchelf) |
| **Overlay** | `takana-cli` | Montar/fusionar/descartar capa de experimentación en caliente |
| **Diario** | `hammerd` | Vigilar mutaciones (fanotify), log append-only, exportar `.swm` |
| **Bus de agente** | `hammerd` | Socket de control para IA/scripts; eventos del sistema |
| **CLI** | `hammer-cli` | El binario `hammer` que orquesta todo lo anterior |
| **Tipos compartidos** | `hammer-core` | `Recipe`, `Swm`, `Hash`, `Store`, formatos serializables |
| **CLI** | `takana-cli` | El binario `takana` que orquesta todo lo anterior |
| **Tipos compartidos** | `takana-core` | `Recipe`, `Swm`, `Hash`, `Store`, formatos serializables |
## 3. Flujo de datos canónico (de la intención al sistema vivo)
1. **Intención** — el humano (o la IA) expresa un cambio: *"que grep ignore binarios por
defecto y use regex Perl"*.
2. **Receta** — se traduce a una `Recipe`: repo + commit fijado + patch + flags + compilador.
3. **Build**`hammer-build` levanta el sandbox, compila con `zig cc`, produce un binario
3. **Build**`takana-build` levanta el sandbox, compila con `zig cc`, produce un binario
musl estático, lo deposita en el store bajo `BLAKE3(...)`.
4. **Overlay** — el artefacto se hidrata, pero **en una capa overlay temporal**, no en el
sistema real. El humano/IA prueba en caliente.
5. **Validación** — corre el test harness; los eventos van por el bus (`BUILD_READY`,
`CRASHED`). Si falla, se descarta el overlay; el sistema base intacto.
6. **Promoción** — si pasa, se fusiona al FHS real. El diario registra la mutación.
7. **Compartir**`hammer export` empaqueta el delta como `.swm`: base-hash + patch + config.
7. **Compartir**`takana export` empaqueta el delta como `.swm`: base-hash + patch + config.
Otro usuario lo `apply`-ea: su IA reproduce y verifica localmente; nunca ejecuta tu binario.
## 4. Estado en disco (layout)
@@ -85,10 +85,10 @@ el host; siempre delega al lab.
## 5. Límites del sistema (qué corre dónde)
- **`hammer-build`** corre privilegiado lo justo para crear namespaces (vía `bubblewrap`,
- **`takana-build`** corre privilegiado lo justo para crear namespaces (vía `bubblewrap`,
que usa user-namespaces sin root real cuando es posible).
- **`hammerd`** corre como servicio bajo el init; expone el bus y vigila el diario.
- **`hammer` (CLI)** corre como el usuario; es el punto de entrada de todo flujo manual.
- **`takana` (CLI)** corre como el usuario; es el punto de entrada de todo flujo manual.
- **La IA** es un cliente del bus como cualquier otro proceso — sin privilegios especiales
más allá de los que el humano le conceda explícitamente (ver [SDD 08](08-ai-integration.md)).
+3 -3
View File
@@ -139,15 +139,15 @@ hermético, cross-compilando al target. El Stage 0 tradicional (cross-compiler m
en gran parte resuelto por `zig`.
Para la fase Alpine no necesitamos bootstrap completo: Alpine ya provee musl y un toolchain;
usamos `zig cc` para los builds de hammer y validamos el flujo. El bootstrap from-scratch real
usamos `zig cc` para los builds de takana y validamos el flujo. El bootstrap from-scratch real
es trabajo del track de distro propia ([SDD 10](10-roadmap.md), [ADR 0003](adr/0003-zig-cc-builder.md)).
## 7. Interfaz (qué expone el crate)
```rust
// hammer-build
// takana-build
pub fn build(recipe: &Recipe, store: &Store) -> Result<ArtifactHash>;
pub fn artifact_hash(recipe: &Recipe, store: &Store) -> Result<ArtifactHash>;
```
CLI: `hammer build <recipe.toml>` → imprime el hash del artefacto sellado.
CLI: `takana build <recipe.toml>` → imprime el hash del artefacto sellado.
+6 -6
View File
@@ -17,7 +17,7 @@ arrastrar la ruta del store grabada a fuego, y sin el infierno de symlinks de Ni
- **Direccionado por contenido.** El prefijo es el `artifact_hash` (ver [SDD 02](02-build-lab.md)).
- **Deduplicado.** Dos recetas que producen el mismo árbol comparten entrada.
- **GC por alcanzabilidad.** Un artefacto es basura si ningún hardlink del FHS ni ningún pin
lo referencia. `hammer gc` libera lo inalcanzable.
lo referencia. `takana gc` libera lo inalcanzable.
## 2. El problema que resolvemos
@@ -52,7 +52,7 @@ contra el store, y la hidratación **reescribe el binario** antes de inyectarlo:
`patchelf` (irónicamente, creado por el equipo de Nix) es la herramienta exacta para esto.
**Implementado** (`hammer-build::hydrate`, `LinkMode::Dynamic` + `DynamicSpec{interpreter,rpath}`):
**Implementado** (`takana-build::hydrate`, `LinkMode::Dynamic` + `DynamicSpec{interpreter,rpath}`):
el walker detecta ELF por magic (`\x7fELF`) y, si el `DynamicSpec` trae interpreter y/o
rpath, **copia** ese binario (no lo hardlinkea: patchelf lo reescribiría y mutaría el inode del
store) a un `.hammer-tmp`, lo parchea con `patchelf --set-interpreter/--set-rpath` y lo renombra
@@ -66,7 +66,7 @@ La idea de "actualizar el sustrato atómicamente y que los binarios no se entere
segura si se versiona explícitamente.** musl **no garantiza estabilidad de ABI entre
versiones**; confiar ciegamente en ello rompería binarios.
Política de `hammer`:
Política de `takana`:
- El grueso del sistema se enlaza **estático** → inmune al problema.
- Un subconjunto hiper-reducido de librerías core de actualización frecuente por seguridad
@@ -95,7 +95,7 @@ limpia reconstruible debajo.
## 7. Interfaz
```rust
// hammer-core
// takana-core
pub struct Store { root: PathBuf }
impl Store {
pub fn has(&self, h: &ArtifactHash) -> bool;
@@ -104,9 +104,9 @@ impl Store {
pub fn gc(&self, roots: &[ArtifactHash]) -> Result<GcReport>;
}
// hammer-build
// takana-build
pub fn hydrate(h: &ArtifactHash, store: &Store, target_fhs: &Path, mode: LinkMode)
-> Result<Vec<HydratedFile>>;
```
CLI: `hammer hydrate <hash> [--into /]` — proyecta el artefacto al FHS (real o de overlay).
CLI: `takana hydrate <hash> [--into /]` — proyecta el artefacto al FHS (real o de overlay).
+5 -5
View File
@@ -22,12 +22,12 @@ nunca se toca.
## 2. El ciclo de vida
```
hammer try # monta un overlay sobre los directorios del sistema
takana try # monta un overlay sobre los directorios del sistema
... experimentas / la IA inyecta binarios y edita configs ...
... pruebas en caliente: ejecutas, rompes, observas ...
hammer commit # fusiona upperdir → FHS real (rsync inteligente) y registra en el diario
takana commit # fusiona upperdir → FHS real (rsync inteligente) y registra en el diario
— o —
hammer discard # desmonta y borra el upperdir: el sistema vuelve al estado base, instantáneo
takana discard # desmonta y borra el upperdir: el sistema vuelve al estado base, instantáneo
```
- **`try`** → estado base limpio + capa mutable encima. Riesgo cero.
@@ -75,11 +75,11 @@ Reglas:
## 6. Interfaz
```rust
// hammer-cli (con helpers de hammer-core)
// takana-cli (con helpers de takana-core)
pub fn overlay_try(targets: &[PathBuf]) -> Result<OverlayId>;
pub fn overlay_commit(id: OverlayId, journal: &Journal) -> Result<CommitReport>;
pub fn overlay_discard(id: OverlayId) -> Result<()>;
pub fn overlay_status() -> Result<Vec<OverlayState>>;
```
CLI: `hammer try` · `hammer commit` · `hammer discard` · `hammer status`.
CLI: `takana try` · `takana commit` · `takana discard` · `takana status`.
+7 -7
View File
@@ -1,6 +1,6 @@
# SDD 05 — Diario de mutaciones
Este es el insight central de `hammer`: **el diario es tu configuración del sistema — sin ser
Este es el insight central de `takana`: **el diario es tu configuración del sistema — sin ser
declarativa.** No declaras tu sistema en un archivo de texto antes de vivirlo; vives el sistema
imperativamente, y el propio sistema deriva, *a posteriori*, un mapa de tu desviación respecto
a la base limpia construida por el laboratorio.
@@ -26,7 +26,7 @@ anota, en silencio, un diario de modificaciones.
**Registra** mutaciones del FHS gestionado:
- creación / reemplazo / borrado de archivos en `/bin`, `/sbin`, `/lib`, `/etc`,
- qué los originó (PID, UID, y si fue una hidratación de `hammer`, el `artifact_hash`),
- qué los originó (PID, UID, y si fue una hidratación de `takana`, el `artifact_hash`),
- ediciones de archivos de config en `/etc`.
**No registra:**
@@ -48,19 +48,19 @@ construcción.)
```
`artifact: null` + `op: manual-replace` ⇒ el usuario reemplazó algo a mano fuera del flujo de
`hammer`. Eso no es un error; es información. El diario distingue "mutación trazable" (vino de
`takana`. Eso no es un error; es información. El diario distingue "mutación trazable" (vino de
una receta/artefacto conocido) de "mutación opaca" (pisada manual), sin prohibir ninguna.
## 4. Exportar: del diario al `.swm`
`hammer export` lee el diario, lo recorta al delta relevante respecto a una **base** conocida,
`takana export` lee el diario, lo recorta al delta relevante respecto a una **base** conocida,
y produce un manifiesto `.swm` ([SDD 06](06-swm-format.md)):
- **base** = el conjunto de commits fijados + versión de distro de los que partiste.
- **mutations** = las mutaciones trazables convertidas a recetas (`source_patch`) y las
ediciones de config (`config_edit`), más reglas de init si aplican.
- Las mutaciones **opacas** (pisadas manuales sin artefacto) se reportan como advertencia: no
se pueden compartir como receta reproducible. `hammer` te dice exactamente cuáles y por qué.
se pueden compartir como receta reproducible. `takana` te dice exactamente cuáles y por qué.
Así, **exportar el diario = exportar tu distro**, pero sólo la parte que es reproducible y
verificable. Lo que pisaste a ciegas, lo sabes y decides qué hacer con ello.
@@ -74,7 +74,7 @@ Tu sistema se replica desde su historia, no desde una declaración.
## 6. Interfaz
```rust
// hammerd / hammer-core
// hammerd / takana-core
pub struct Journal { path: PathBuf }
impl Journal {
pub fn record(&self, ev: MutationEvent) -> Result<()>;
@@ -83,4 +83,4 @@ impl Journal {
}
```
CLI: `hammer journal` (ver/seguir el diario) · `hammer export [--base <ref>] > my.swm`.
CLI: `takana journal` (ver/seguir el diario) · `takana export [--base <ref>] > my.swm`.
+23 -23
View File
@@ -1,6 +1,6 @@
# SDD 06 — Formato `.swm` (Software Mutación)
El `.swm` es la unidad de intercambio de `hammer`. **No es un paquete binario** (`.deb`,
El `.swm` es la unidad de intercambio de `takana`. **No es un paquete binario** (`.deb`,
`.rpm`, ni un `PKGBUILD` que compila quién-sabe-qué). Es un **manifiesto de la mutación**: la
receta de transformación sobre código fuente público, más las ediciones de configuración. El
receptor reproduce y verifica localmente; **nunca ejecuta tu binario** (ver
@@ -13,7 +13,7 @@ receptor reproduce y verifica localmente; **nunca ejecuta tu binario** (ver
swm_version: 1
base:
distro_version: "2026-06-06" # versión del set base de hammer
distro_version: "2026-06-06" # versión del set base de takana
pins: # commits fijados de los upstreams relevantes
kernel: "a1b2c3d…"
musl: "e4f5a6b…"
@@ -55,7 +55,7 @@ signature: # opcional pero recomendado (ver SDD 09)
| `type` | Qué describe | Cómo lo aplica el receptor |
|---|---|---|
| `source_patch` | recompilar una herramienta desde fuente parcheada (git **o** tarball) | clona repo@commit / baja tarball+sha256 → aplica patch → `hammer build` → hidrata en overlay |
| `source_patch` | recompilar una herramienta desde fuente parcheada (git **o** tarball) | clona repo@commit / baja tarball+sha256 → aplica patch → `takana build` → hidrata en overlay |
| `config_edit` | edición de un archivo de config | aplica el `inline_diff` (3-way) sobre el archivo objetivo |
| `init_rule` | regla de supervisión de un servicio | materializa `/etc/hammer/init.d/{service}.rule` (TOML: service/action/command); `disable`/`stop` la retiran. El init (arje) lee ese árbol |
| `file_drop` | depositar un archivo de datos no compilable | escribe el archivo (con su hash declarado) en la ruta |
@@ -70,10 +70,10 @@ signature: # opcional pero recomendado (ver SDD 09)
Cada `source_patch` es, en esencia, una `Recipe` ([SDD 02](02-build-lab.md)) serializada para
viajar. El `build` lleva el **compilador por mutación** (no global).
## 3. Aplicación (`hammer apply`)
## 3. Aplicación (`takana apply`)
```
hammer apply my-grep-tweak.swm
takana apply my-grep-tweak.swm
```
1. **Verifica la base.** ¿Los `pins`/`distro_version` son compatibles con el sistema local?
@@ -83,7 +83,7 @@ hammer apply my-grep-tweak.swm
en el lab local → obtiene un `artifact_hash`.
4. **Aísla.** Hidrata todo en un **overlay temporal** ([SDD 04](04-overlay.md)); aplica
`config_edit`/`init_rule`/`file_drop` en esa capa. **No toca el sistema base.**
5. **Valida.** Corres/pruebas en caliente. Si bien → `hammer commit`. Si mal → `hammer discard`.
5. **Valida.** Corres/pruebas en caliente. Si bien → `takana commit`. Si mal → `takana discard`.
Nunca se ejecuta un binario ajeno: sólo se transforma código fuente público con una receta que
tu propio laboratorio compila.
@@ -93,7 +93,7 @@ tu propio laboratorio compila.
Como el lab es determinista ([SDD 02](02-build-lab.md)), el `artifact_hash` que produce tu
máquina debe coincidir con el del autor (si declara `expected_hash`). Coincidencia ⇒ obtuviste
exactamente el mismo binario, sin haberlo descargado. Discrepancia ⇒ algo difiere (toolchain,
pin, patch) y `hammer` te dice qué. Esto es "verificar, no confiar" en la práctica.
pin, patch) y `takana` te dice qué. Esto es "verificar, no confiar" en la práctica.
## 5. No-objetivos del formato
@@ -106,7 +106,7 @@ pin, patch) y `hammer` te dice qué. Esto es "verificar, no confiar" en la prác
## 6. Interfaz
```rust
// hammer-core
// takana-core
#[derive(Serialize, Deserialize)]
pub struct Swm { /* swm_version, base, mutations, signature */ }
impl Swm {
@@ -119,16 +119,16 @@ impl Swm {
CLI:
- `hammer apply <file.swm>` — abre un overlay por defecto y aplica las mutaciones. Con
- `takana apply <file.swm>` — abre un overlay por defecto y aplica las mutaciones. Con
`--prefix DIR` opera directamente bajo `DIR` sin overlay (tests, staging). Con
`--base-ref base.json` aborta si la base local no es compatible.
- `hammer swm-verify <file.swm>` — chequea schema, hashes de los `file_drop` inline y,
- `takana swm-verify <file.swm>` — chequea schema, hashes de los `file_drop` inline y,
con `--base-ref`, la compatibilidad de base.
- `hammer export --journal DIR > out.swm` — lee el diario y emite un `.swm` con un
- `takana export --journal DIR > out.swm` — lee el diario y emite un `.swm` con un
`file_drop` por archivo modificado (estado final actual; los `Delete` se omiten). El
receptor reproduce byte-a-byte. (Cuando el diario tiene provenance de receta, `export`
emite `source_patch` en vez de `file_drop` — el mapa artefacto→receta ya existe.)
- `hammer pack <recipe.toml>`**dirección forward (Etapa F):** empaqueta una receta del
- `takana pack <recipe.toml>`**dirección forward (Etapa F):** empaqueta una receta del
corpus como un `.swm` de un único `source_patch`. Vuelve una receta que el sistema ya sabe
construir un paquete distribuible y reproducible-desde-fuente; es la inversa de `apply`.
`--target-bin` fija el ancla de sanity (default `/usr/bin/<name>`); `--expected b3:…` o
@@ -137,7 +137,7 @@ CLI:
`strip_components`, y las `deps` por nombre; los patches de la receta viajan inline
(concatenados). Si la receta declara build-deps, `pack` recuerda publicarlas en el mismo repo.
Con `--repo DIR` **publica** en un repositorio (ver abajo) en vez de (o además de) `--out`.
- `hammer install <nombre> [--repo DIR|URL]`**consume** del repositorio: resuelve `nombre` en
- `takana install <nombre> [--repo DIR|URL]`**consume** del repositorio: resuelve `nombre` en
el índice, verifica la firma del release y la del `.swm` (con `--trust DIR`) y la base (con
`--base-ref`), **resuelve el cierre transitivo de build-deps** desde el índice (orden
topológico) poblando un catálogo de recetas, y delega en el camino de `apply` (reproduce el
@@ -147,20 +147,20 @@ CLI:
`.swm`; con URL baja el índice + el cierre de deps a un temporal y procede igual).
`--prefix`/`--skip-source-patch` para staging y dry-run de schema. `--require-signed` exige que
el release esté firmado por una clave confiada (modo estricto: no basta con reproducir).
- `hammer repo list [--repo DIR]` — lista el catálogo (`<repo>/index.json`).
- `hammer repo sign --repo DIR --key KEY` — firma el ÍNDICE entero (release). Re-firmá tras
- `takana repo list [--repo DIR]` — lista el catálogo (`<repo>/index.json`).
- `takana repo sign --repo DIR --key KEY` — firma el ÍNDICE entero (release). Re-firmá tras
publicar (cada `pack --repo` invalida la firma del release).
- `hammer repo verify --repo DIR [--trust DIR]` — verifica la firma del release.
- `hammer uninstall <nombre> [--db FILE]` — borra los ficheros que el paquete registró (refcount:
- `takana repo verify --repo DIR [--trust DIR]` — verifica la firma del release.
- `takana uninstall <nombre> [--db FILE]` — borra los ficheros que el paquete registró (refcount:
respeta los que otro paquete instalado también aporta) y lo quita de la DB de instalados.
- `hammer installed [--db FILE]` — lista los paquetes instalados. `install` registra cada paquete
- `takana installed [--db FILE]` — lista los paquetes instalados. `install` registra cada paquete
(nombre, versión, hash, ficheros creados) en la DB (`/var/lib/hammer/installed.json` por defecto).
- `hammer import-nix [FILE]`**Etapa G (poblar el catálogo desde nixpkgs):** toma el JSON
- `takana import-nix [FILE]`**Etapa G (poblar el catálogo desde nixpkgs):** toma el JSON
normalizado de un paquete nix (lo produce `scripts/nix-import.sh <attr>` vía `nix eval`) y emite
una receta hammer. Trae la RECETA (source+hash+deps+flags), NUNCA el binario del cache de nix —
hammer reconstruye desde fuente igual ("verificar, no confiar"). `fetchurl` plano → `tarball`
una receta takana. Trae la RECETA (source+hash+deps+flags), NUNCA el binario del cache de nix —
takana reconstruye desde fuente igual ("verificar, no confiar"). `fetchurl` plano → `tarball`
+sha256 (hash nix→hex); GitHub → `repo`+`commit`. Es un PUNTO DE PARTIDA: el build en el lab de
hammer (zig/musl) suele necesitar adaptación por paquete. Pipeline: `nix-import.sh hello`
takana (zig/musl) suele necesitar adaptación por paquete. Pipeline: `nix-import.sh hello`
receta → `pack``.swm` → repo → `install` (reproduce desde fuente). Es cómo el catálogo crece
de 34 recetas a-mano a miles sin reescribirlas.
@@ -173,7 +173,7 @@ X"); la identidad la asigna el repo al publicar — el índice es el namespace.
publica con el nombre/versión de la receta (upsert idempotente por nombre; una versión nueva
retira el `.swm` huérfano). El repo son ficheros estáticos, así que `install --repo URL` lo
consume por HTTP(S) (cualquier servidor estático sirve; baja `index.json` + el cierre de deps a
un temporal). Tipos en `hammer-core::repo` (`RepoIndex`, `PackageEntry`).
un temporal). Tipos en `takana-core::repo` (`RepoIndex`, `PackageEntry`).
**Dependencias.** Un `source_patch` lleva sus build-deps por NOMBRE (las de la receta original);
la `PackageEntry` las espeja para resolver el grafo sin abrir cada `.swm`. `install <nombre>`
+1 -1
View File
@@ -95,4 +95,4 @@ pub async fn serve_agent_bus(sock: &Path, caps_policy: CapsPolicy) -> Result<()>
pub fn init_control_send(line: &str) -> Result<()>; // escribe en /run/init.control
```
CLI de conveniencia: `hammer ctl "start web"` (humano) · la IA usa el socket directamente.
CLI de conveniencia: `takana ctl "start web"` (humano) · la IA usa el socket directamente.
+7 -7
View File
@@ -28,7 +28,7 @@ a través del **bus de agente** ([SDD 07](07-agent-bus.md)) y de un **overlay**
└──────┬──────┘
┌─────────────┐
│ TRY │ hammer try → overlay temporal; INJECT del artefacto y de las
│ TRY │ takana try → overlay temporal; INJECT del artefacto y de las
│ (overlay) │ ediciones de config en la capa. El sistema real intacto.
└──────┬──────┘
@@ -43,7 +43,7 @@ a través del **bus de agente** ([SDD 07](07-agent-bus.md)) y de un **overlay**
└──────┬──────┘
┌─────────────┐
│ HUMANO │ tú decides: hammer commit (promueve + diario) o hammer discard.
│ HUMANO │ tú decides: takana commit (promueve + diario) o takana discard.
│ decide │ La IA NUNCA promueve al FHS real por su cuenta (ver §4).
└─────────────┘
```
@@ -60,7 +60,7 @@ a través del **bus de agente** ([SDD 07](07-agent-bus.md)) y de un **overlay**
- **Mutabilidad + overlay** → agencia real con red de seguridad; sin rollbacks pesados.
En distros inmutables/declarativas, la IA tendría que lidiar con generadores de config y
políticas read-only; en tradicionales, no tendría provenance. `hammer` mantiene las **tuberías
políticas read-only; en tradicionales, no tendría provenance. `takana` mantiene las **tuberías
expuestas y de baja entropía**, que es justo lo que un agente necesita.
## 4. Garantías de seguridad (no negociables)
@@ -78,7 +78,7 @@ expuestas y de baja entropía**, que es justo lo que un agente necesita.
## 5. De la intención al `.swm`: el rol del modelo
El traductor intención→`.swm` se conecta detrás del trait `IntentTranslator` del crate
`hammer-agent`. Contrato:
`takana-agent`. Contrato:
- **Entrada:** intención en NL + `SystemContext` (lo que el orquestador recolecta por
`QUERY`: `BaseRef` local, extras JSON).
@@ -92,7 +92,7 @@ enchufa sin tocar el `Orchestrator`.
**El modelo nunca ejecuta nada directamente:** emite comandos por el bus, que `hammerd`
valida contra las capacidades concedidas, y el `Orchestrator` traduce las mutaciones a
`hammer-core::apply` o a `Compile` por el bus.
`takana-core::apply` o a `Compile` por el bus.
## 6. Lenguaje de consulta/transformación (visión, Fase posterior)
@@ -109,7 +109,7 @@ esté probado.
## 7. Interfaz
El crate `hammer-agent` (Fase 6) expone:
El crate `takana-agent` (Fase 6) expone:
```rust
// hammer_agent::client
@@ -138,5 +138,5 @@ impl<T> Orchestrator<T> {
```
Los tipos del protocolo (`Command`/`Event`/`Cap`/`Peer`/`RecipeInline`) viven en
`hammer-core::proto` para ser compartidos por `hammerd`, `hammer-agent` y cualquier cliente
`takana-core::proto` para ser compartidos por `hammerd`, `takana-agent` y cualquier cliente
externo escrito en Rust.
+5 -5
View File
@@ -1,6 +1,6 @@
# SDD 09 — Modelo de confianza
La premisa de compartir en `hammer` es **"verificar, no confiar"**: nunca ejecutas un binario
La premisa de compartir en `takana` es **"verificar, no confiar"**: nunca ejecutas un binario
ajeno; reproduces una receta sobre código fuente público y compruebas que obtienes el mismo
resultado. Esto sólo funciona si el build es determinista y si el intercambio está firmado y,
opcionalmente, anclado a un log de transparencia.
@@ -16,7 +16,7 @@ opcionalmente, anclado a un log de transparencia.
¿coincide con el expected_hash del autor?
├─ sí → obtuviste exactamente su binario, sin descargarlo. Confianza derivada del código, no del autor.
└─ no → algo difiere (pin, patch, toolchain). hammer te dice qué. No promueves.
└─ no → algo difiere (pin, patch, toolchain). takana te dice qué. No promueves.
```
El binario del autor **nunca viaja**. Viaja la *receta*. La confianza no está en "el binario de
@@ -47,7 +47,7 @@ mutations). Sirve para:
- **Integridad:** que no se alteró en tránsito.
La firma **no** sustituye la verificación reproducible — un autor firmado podría declarar un
`expected_hash` que no corresponde a su patch; por eso `hammer apply` **siempre** reproduce y
`expected_hash` que no corresponde a su patch; por eso `takana apply` **siempre** reproduce y
compara, firme o no.
## 4. Log de transparencia (visión, opt-in)
@@ -72,7 +72,7 @@ Claves públicas en las que el usuario confía para *autoría* (no para ejecuci
comunidad-foo.ed25519.pub
```
`hammer apply` reporta el estado de firma (`trusted` / `unknown-key` / `bad-sig` / `unsigned`)
`takana apply` reporta el estado de firma (`trusted` / `unknown-key` / `bad-sig` / `unsigned`)
pero la decisión de promover sigue dependiendo de la **verificación reproducible** + el
`commit` humano.
@@ -89,7 +89,7 @@ pero la decisión de promover sigue dependiendo de la **verificación reproducib
## 7. Interfaz
```rust
// hammer-core
// takana-core
pub fn sign_swm(swm: &Swm, key: &Ed25519PrivateKey) -> Signature;
pub fn verify_swm(swm: &Swm, trust: &TrustStore) -> SigStatus;
pub fn reproduce_and_compare(swm: &Swm, store: &Store) -> Result<Vec<HashMatch>>;
+54 -54
View File
@@ -2,11 +2,11 @@
## Estrategia: validar la capa AI-nativa sobre Alpine antes de la distro propia
La innovación de `hammer` no es la distro — es el substrato AI-nativo (build determinista +
La innovación de `takana` no es la distro — es el substrato AI-nativo (build determinista +
mutable + diario + `.swm`). Construir bootstrap + init + userland de cero antes de validar esa
capa sería arriesgar meses contra una hipótesis no probada. Por eso:
> **Fase 06:** montamos `hammer` **sobre Alpine** (musl + busybox + FHS mutable, ya cocidos).
> **Fase 06:** montamos `takana` **sobre Alpine** (musl + busybox + FHS mutable, ya cocidos).
> Validamos el bucle completo en semanas.
> **Track posterior:** una vez probado, bajamos a distro propia (init propio, bootstrap propio,
> userland propio) reusando todo el tooling sin retrabajo.
@@ -18,17 +18,17 @@ Ver [ADR 0002](adr/0002-alpine-first.md).
## Fases (sobre Alpine)
### Fase 0 — Laboratorio de build ✅
- [x] `hammer-core`: tipos `Recipe`, `ArtifactHash`, `Store` (BLAKE3, layout `/store`).
- [x] `hammer-build`: sandbox con `bubblewrap` + `zig cc` → binario musl estático.
- [x] `takana-core`: tipos `Recipe`, `ArtifactHash`, `Store` (BLAKE3, layout `/store`).
- [x] `takana-build`: sandbox con `bubblewrap` + `zig cc` → binario musl estático.
- [x] CAS: hashing de entrada, caché por hash, sellado en el store.
- [x] `hammer build <recipe.toml>` → imprime el hash del artefacto.
- [x] `takana build <recipe.toml>` → imprime el hash del artefacto.
- [x] Fuente git **y** tarball (sha256 fijado), patches opcionales.
- [x] Heurística de build system: autotools / cmake / meson / make plano.
- [x] Caché persistente de zig entre builds (~30s/build ahorrados).
### Fase 1 — Hidratación ✅
- [x] `hydrate(hash, target, mode)`: hardlink a FHS, `patchelf` para el caso dinámico.
- [x] `hammer hydrate <hash> --into /`.
- [x] `takana hydrate <hash> --into /`.
### 🎯 Primer entregable (Fase 0 + 1) ✅
**GNU grep 3.12 compilado estático desde el tarball upstream, sellado en el store
@@ -38,11 +38,11 @@ substrato Alpine. La VM/LXC dedicada queda como ejercicio de empaque, no como
pre-requisito de validación.
### Fase 2 — Overlay de experimentación ✅
- [x] Crate `hammer-overlay`: `try` / `commit` / `discard` / `status` sobre `overlayfs`.
- [x] Crate `takana-overlay`: `try` / `commit` / `discard` / `status` sobre `overlayfs`.
- [x] Manifiesto persistente en `<state_root>/<id>/state.json`; `status` enumera.
- [x] `commit` promociona archivos y procesa whiteouts (char dev 0/0) como remove.
- [x] Subcomandos del CLI: `hammer try [targets…]`, `commit <id>`, `discard <id>`, `status`.
- [x] Tests E2E con bwrap+user-ns, gateados en `HAMMER_OVERLAY_TESTS=1` (kernel-dependiente).
- [x] Subcomandos del CLI: `takana try [targets…]`, `commit <id>`, `discard <id>`, `status`.
- [x] Tests E2E con bwrap+user-ns, gateados en `TAKANA_OVERLAY_TESTS=1` (kernel-dependiente).
- [x] Hook al diario en `commit` (Fase 3 lo añadió vía `commit_with_journal`).
- [x] Anidación real de overlays. Un `try` sobre un target ya cubierto apila un overlay nuevo
(el kernel usa el merged view inferior como `lowerdir`). Orden de apilamiento total y
@@ -51,17 +51,17 @@ pre-requisito de validación.
detecta capas más jóvenes que solapan targets (igualdad o ancestro de path) y la operación
falla con `Error::Shadowed` si las hay. Guard cubierto por 6 unit tests; el stack real
(capa 2 ve la 1, LIFO rechaza commit de abajo, commit arriba→abajo fusiona) por
`overlay_nested_stack_inside_userns`, gated en `HAMMER_OVERLAY_TESTS`. Pendiente menor:
`overlay_nested_stack_inside_userns`, gated en `TAKANA_OVERLAY_TESTS`. Pendiente menor:
unicidad de orden entre procesos concurrentes (track posterior).
### Fase 3 — Diario de mutaciones ✅
- [x] Crate `hammer-journal`: `MutationEvent`, `Source` (HammerHydrate/HammerCommit/External),
- [x] Crate `takana-journal`: `MutationEvent`, `Source` (HammerHydrate/HammerCommit/External),
append/read/tail/follow JSON-líneas. RFC 3339 sin chrono.
- [x] `hammerd` con `fanotify` (FAN_CLOSE_WRITE) sobre `/bin`,`/sbin`,`/usr/bin`,`/usr/sbin`,
`/lib`,`/usr/lib`,`/etc`. Filtra eventos bajo overlays activos. Fallback graceful sin
CAP_SYS_ADMIN.
- [x] `hammer journal [--tail N] [--follow] [--format pretty|json]`.
- [x] `hammer commit` registra cada archivo promocionado vía `commit_with_journal`.
- [x] `takana journal [--tail N] [--follow] [--format pretty|json]`.
- [x] `takana commit` registra cada archivo promocionado vía `commit_with_journal`.
- [x] Refinar la `op` del watcher. `watcher::classify_op(&journal, path, deleted)` deriva
`Delete` (el fd resuelve a `" (deleted)"`, que ahora recortamos del path en vez de
colarlo al diario), `Edit` (el diario ya tenía un evento previo no-`Delete` del path) o
@@ -84,34 +84,34 @@ pre-requisito de validación.
- [x] `apply_config_edit` (hunks `-/+` con búsqueda exacta, no-fuzz) y `apply_file_drop`
(base64 inline + verificación BLAKE3 vs `content_hash`).
- [x] Bridge `Mutation::SourcePatch``Recipe` + `build` + hidratación.
- [x] CLI: `hammer apply [--prefix DIR] [--base-ref base.json]`,
`hammer swm-verify`, `hammer export --journal DIR > out.swm`.
- [x] CLI: `takana apply [--prefix DIR] [--base-ref base.json]`,
`takana swm-verify`, `takana export --journal DIR > out.swm`.
- [x] `patch_url` / `content_url` remotos. `hammer_build::download` (curl, sólo fuera del
sandbox; acepta `file://` para tests offline). `build_source_patch` descarga `patch_url`
al `swm-recipes/<commit>.patch` antes de compilar; `hammer apply` descarga `content_url`
al `swm-recipes/<commit>.patch` antes de compilar; `takana apply` descarga `content_url`
y **verifica BLAKE3 antes de escribir** (hash erróneo ⇒ nada tocado). Su integridad la
cubre `content_hash` (file_drop) y el build reproducible + `expected_hash` (patch).
- [x] Provenance en `export`: mapa artefacto→receta vía sidecar `.hammer/recipe.toml` que
`hammer-build::build` escribe dentro del artefacto antes de sellar. `hammer export`
`takana-build::build` escribe dentro del artefacto antes de sellar. `takana export`
agrupa eventos por `artifact_hash` y emite UN `source_patch` por grupo cuya receta
sea recuperable; lo demás cae al fallback `file_drop`. Source `git` **y `tarball`**
modelados: `Mutation::SourcePatch` (y `RecipeInline`) llevan campos `repo`/`commit`
**xor** `tarball`/`sha256` (resueltos por `swm::swm_source_kind`, misma regla que
`recipe::Source::kind`); `hammer export` reconstruye ambos sin caer a `file_drop`.
`recipe::Source::kind`); `takana export` reconstruye ambos sin caer a `file_drop`.
- [x] Firma `signature` (ed25519) y `TrustStore` local. `hammer_core::sign`: `KeyPair`
(genera/carga/escribe claves), `TrustStore::load(dir)` (lee `*.ed25519.pub`),
`Swm::verify_signature(&trust) → SigStatus` (`trusted`/`unknown-key`/`bad-sig`/
`unsigned`) sobre bytes canónicos JSON del manifiesto sin la firma. CLI:
`hammer keygen`, `hammer swm-sign`, y `hammer swm-verify --trust DIR`. La firma reporta
`takana keygen`, `takana swm-sign`, y `takana swm-verify --trust DIR`. La firma reporta
autoría/integridad; NO autoriza promover (sigue mandando reproducir + commit). SDD 09 §3,§5.
- **Hecho cuando:** exportas un cambio, lo aplicas en otra máquina y reproduce idéntico.
✅ Demostrado en `crates/hammer-cli/tests/swm_roundtrip.rs` para
`config_edit` + `file_drop`. `source_patch` reusa el camino de Fase 0/1 (gated en
`HAMMER_NETWORK_TESTS`).
`TAKANA_NETWORK_TESTS`).
### Fase 5 — Bus de agente ✅ *(queda `CRASHED` real, diferido al track posterior)*
- [x] `/run/init.control` (FIFO humano): `mkfifo`, reader que loguea cada línea, y
`hammer ctl <line>` que escribe al FIFO con error claro si no existe.
`takana ctl <line>` que escribe al FIFO con error claro si no existe.
- [x] `/run/agent.sock` (JSON-líneas) con handshake `Hello/Welcome`, auth via
`SO_PEERCRED` y `CapsPolicy` por UID.
- [x] Comandos `Compile`/`Inject`/`Query`/`Init`; eventos
@@ -136,10 +136,10 @@ pre-requisito de validación.
socket. ✅ Camino implementado (`Compile``BuildReady`/`BuildFailed`) y la maquinaria
alrededor cubierta por `crates/hammerd/tests/bus_e2e.rs`: handshake con peer creds,
gating `no_cap`, `Query`, `Modified` fan-out, `Init`→FIFO. Un `Compile` real reusa el
camino de Fase 0/1 (gated en `HAMMER_NETWORK_TESTS`).
camino de Fase 0/1 (gated en `TAKANA_NETWORK_TESTS`).
### Fase 6 — Integración de la IA ✅
- [x] Crate `hammer-agent` con tres piezas:
- [x] Crate `takana-agent` con tres piezas:
- `AgentClient`: cliente síncrono del bus (handshake, `compile`/`inject`/`query`/`init`
bloqueantes, drenado de eventos asíncronos).
- `IntentTranslator` (trait) + `MockTranslator` cargado desde un `IntentCatalog` YAML
@@ -148,30 +148,30 @@ pre-requisito de validación.
- `Orchestrator` con `run(intent) → Proposal`: plan → schema → base → try (overlay
o prefix) → apply (mutaciones puras + source_patch vía bus opcional) → verify →
propose.
- [x] CLI: `hammer ai <intent> --catalog F [--prefix DIR --base-ref F --bus SOCK]`.
- [x] Tipos del protocolo del bus movidos a `hammer-core::proto` para que `hammerd` y
`hammer-agent` los compartan.
- [x] CLI: `takana ai <intent> --catalog F [--prefix DIR --base-ref F --bus SOCK]`.
- [x] Tipos del protocolo del bus movidos a `takana-core::proto` para que `hammerd` y
`takana-agent` los compartan.
- [x] Tests:
- 10 unit en `hammer-agent` (translator + catalog + orchestrator).
- 10 unit en `takana-agent` (translator + catalog + orchestrator).
- 3 e2e del bucle agéntico (prefix tmp → archivos esperados en disco).
- 1 e2e del cliente contra un *stub* del bus (handshake + Compile→BuildReady + Modified
asíncrono).
- [x] Traductor LLM real (Claude API u otro), opcional vía feature flag o crate aparte.
`ClaudeTranslator` detrás de la feature `llm-claude`, con `ureq + rustls`. Modelo
por defecto `claude-opus-4-8`, adaptive thinking + `effort=high`. Trait `HttpClient`
inyectable para tests sin red. CLI: `hammer ai --llm` (mutuamente excluyente con
inyectable para tests sin red. CLI: `takana ai --llm` (mutuamente excluyente con
`--catalog`). Sin la feature, el flag falla con mensaje claro.
- [x] Lenguaje de consulta del sistema (SDD 08 §6) para que la IA refiera servicios y
archivos sin rutas frágiles. Forma `kind:value` (`bin`, `file`, `pin`, `service`,
`depends`), evaluable local (`hammer query <expr>`) y remoto vía bus
`depends`), evaluable local (`takana query <expr>`) y remoto vía bus
(`Command::Query{what:"expr"}`). Parser ELF64 mínimo para extraer `DT_NEEDED`.
- [x] Bucle de auto-reparación (cliente reacciona a `Crashed` con un nuevo plan).
`Orchestrator::run_with_repair(intent, &RepairPolicy)` drena async events del bus
tras cada apply; si llega `Crashed`, formatea una nueva intención y reentra hasta
`max_attempts`. El `Proposal` final lleva el `repair_chain` completo para que el
humano vea la evolución antes de hacer commit. CLI: `hammer ai --repair-max-attempts`.
humano vea la evolución antes de hacer commit. CLI: `takana ai --repair-max-attempts`.
- **Hecho cuando:** una intención en lenguaje natural produce un cambio probado en overlay,
presentado para `commit` humano. ✅ Demostrado por `hammer ai` con `MockTranslator`:
presentado para `commit` humano. ✅ Demostrado por `takana ai` con `MockTranslator`:
intent → `.swm` → mutaciones aplicadas → `Proposal` con `overlay_id` + checks. El humano
decide `commit`/`discard`. El upgrade a LLM real reusa todo el bucle.
@@ -182,12 +182,12 @@ pre-requisito de validación.
Diseño completo en [SDD 11 — Bootstrap from-scratch](11-bootstrap.md). Resumen:
- **Bootstrap from-scratch** en tres etapas ([SDD 11 §3](11-bootstrap.md)):
- **Stage 0** ✅ — crate `hammer-bootstrap` + `hammer bootstrap stage0`: ingiere el toolchain
- **Stage 0** ✅ — crate `takana-bootstrap` + `takana bootstrap stage0`: ingiere el toolchain
semilla pinned (`SeedSpec`, hash por identidad), verifica sha256 antes de sellar,
idempotente. Cubierto por 4 unit + 1 e2e de CLI (offline, `file://`).
- **Stage 1** ✅ — userland mínimo, **booteado end-to-end en QEMU**. Recetas pinned `musl` 1.2.5 +
`busybox` 1.36.1 (estáticas, zig cc) + `hammerd` + `arje-zero` (recetas Cargo: repo pinned +
deps vendoreadas en el fetch, build nativo crt-static vía `cargo rustc`); `hammer bootstrap
deps vendoreadas en el fetch, build nativo crt-static vía `cargo rustc`); `takana bootstrap
stage1` los construye con la semilla, ensambla el rootfs FHS y lo sella. El init es **arje-zero
como PID 1** (`/sbin/init`→arje-zero) con su seed card `/ente/seed.card.json`; al bootear,
arje-zero carga+valida la seed y **supervisa hammerd + getty** (el `agent.sock` y el watcher
@@ -208,7 +208,7 @@ Diseño completo en [SDD 11 — Bootstrap from-scratch](11-bootstrap.md). Resume
real que la Fase 5 dejó diferido. **Decisión: adoptar `arje`** (init from-scratch ya existente
en el monorepo `tawasuyu`, con PID 1 + supervisión real) en lugar de escribir uno nuevo —
ver [ADR 0007](adr/0007-arje-como-init-propio.md). Trae bus único, CAS BLAKE3 compartido y
confianza en capas (procedencia hammer + atestación arje).
confianza en capas (procedencia takana + atestación arje).
- Userland propio (busybox/toybox a elección), `/etc/service/*` propios.
- Decidir particionado/montaje de `/store` y `/var/lib/hammer`.
- Log de transparencia para compartir en comunidad ([SDD 09](09-trust-model.md) §4): el
@@ -223,8 +223,8 @@ Diseño completo en [SDD 11 — Bootstrap from-scratch](11-bootstrap.md). Resume
agente → bucle agéntico con IA (mock + Claude real tras `llm-claude`).
- ✅ El bucle completo está validado: una intención en lenguaje natural produce un cambio
probado en overlay, presentado para `commit` humano.
- ✅ 214 tests verdes en el workspace (1 e2e de overlayfs gated en `HAMMER_OVERLAY_TESTS`;
los de red en `HAMMER_NETWORK_TESTS`).
- ✅ 214 tests verdes en el workspace (1 e2e de overlayfs gated en `TAKANA_OVERLAY_TESTS`;
los de red en `TAKANA_NETWORK_TESTS`).
- ⏳ Único ítem de las fases diferido: `CRASHED` real (necesita supervisión de servicios →
init propio del track posterior).
-**Track posterior arrancado y con su hito mayor cerrado:** bootstrap from-scratch
@@ -232,17 +232,17 @@ Diseño completo en [SDD 11 — Bootstrap from-scratch](11-bootstrap.md). Resume
auto-alojamiento bit-a-bit confirmado** (`✓ REPRODUCIBLE`, host↔VM). Baseline reproducible
4/4: `of_tree(stage1)=b3:0039b2b9…` (cu=1).
-**CERRADO — auto-alojamiento *puro* (variante b, [SDD 11 §7.2b](11-bootstrap.md)):** que
hammer construya el toolchain del builder desde fuente (no Alpine), reemplazando piezas una a una
takana construya el toolchain del builder desde fuente (no Alpine), reemplazando piezas una a una
con Stage 2 verificando cada paso. **Pieza 1 hecha:** GNU make `4.4.1` (`recipes/make.toml`) se
compila desde fuente con el lab — estática musl, sellada `b3:fbad44ac…`, reproducible bit-a-bit.
**Swap implementado:** `BuilderSpec.swaps` + el flag `hammer bootstrap builder --swap make=<hash>`
montan el `make` hammer sobre `/toolchain/usr/bin/make` (pisando el de Alpine) y lo anclan en el
**Swap implementado:** `BuilderSpec.swaps` + el flag `takana bootstrap builder --swap make=<hash>`
montan el `make` takana sobre `/toolchain/usr/bin/make` (pisando el de Alpine) y lo anclan en el
**hash lógico** del builder ⇒ la procedencia deja de ser "todo Alpine" y se vuelve auditable
(pura `b3:8a370f5d…` vs swapped `b3:e7e2282c…` en el host). El verify lo expone con `SWAP_MAKE=1`.
**Stage 2 in-VM con el swap (2026-06-13): `✗ DIVERGENTE` → causa raíz hallada y arreglada, make
inocente.** El swap dio `of_tree(stage1')=9adefb82… ≠ 0039b2b9…`, pero el control **sin** swap dio el
*mismo* `9adefb82…` ⇒ el make no era la causa. Bisección (descartando uno a uno: musl/busybox idénticos
con hammer-make incluso a `-j1`; hammerd reproduce): el único flaky era **arje-zero**, no-determinista
con takana-make incluso a `-j1`; hammerd reproduce): el único flaky era **arje-zero**, no-determinista
**incluso host-a-host** (~62 KB de diferencia, mismo rustc/commit/flags) — el backend **paralelo** de
rustc/LLVM (ThinLTO) que `codegen-units=1` NO serializa. Builds seriales (la VM tiene 1 vCPU, o
`taskset -c 0`) reproducían bit-a-bit a `9adefb82…`; los paralelos divergían. El viejo `0039b2b9…` era
@@ -251,8 +251,8 @@ Diseño completo en [SDD 11 — Bootstrap from-scratch](11-bootstrap.md). Resume
da byte-idéntico al serial. Baseline del host refrescado y `EXPECT_REF` re-baseado a `9adefb82…`.
**Pieza 2 — busybox (sh+coreutils) validada en host:** el `/toolchain` Alpine es mixto (busybox para
sh/sed/grep/awk/tar/find, GNU coreutils para cp/mkdir/install); el swap monta el busybox estático de
hammer sobre `/toolchain/bin/busybox` (`--swap busybox=<hash>:bin/busybox`, sin código nuevo).
Con make+busybox de hammer los 4/4 (incl. busybox compilándose a sí mismo con hammer-busybox de shell)
takana sobre `/toolchain/bin/busybox` (`--swap busybox=<hash>:bin/busybox`, sin código nuevo).
Con make+busybox de takana los 4/4 (incl. busybox compilándose a sí mismo con takana-busybox de shell)
reproducen `of_tree=9adefb82…`. Expuesto con `SWAP_BUSYBOX=1`. **✓ REPRODUCIBLE in-VM con ambos swaps
(2026-06-13):** `KVM=1 MEM=24576 SWAP_MAKE=1 SWAP_BUSYBOX=1 ./scripts/selfhost-verify.sh` en `libre`
reconstruyó los 4/4 dentro de la VM con el `/toolchain` hammerizado (make `fbad44ac…` + busybox
@@ -268,12 +268,12 @@ Diseño completo en [SDD 11 — Bootstrap from-scratch](11-bootstrap.md). Resume
distintos a la salida vainilla; 3 de ellos (`scsi/{scsi,scsi_ioctl,sg}.h`, legacy userspace, NO UAPI)
son **requeridos** porque el applet `eject` de busybox los incluye y sin ellos no compila. Se aportan
content-pinned vía `recipes/linux-headers-alpine-compat.patch` (+ 2 cosméticos: kernel.h/if_tunnel.h) y
se pisan sobre la salida. Criterio de éxito fuerte alcanzado: **`diff -r` del header-tree hammer contra
se pisan sobre la salida. Criterio de éxito fuerte alcanzado: **`diff -r` del header-tree takana contra
el de Alpine = vacío** ⇒ el toolchain swapeado es byte-idéntico al baseline ⇒ todo lo que compile abajo
reproduce de por sí (garantía por construcción, más fuerte que un rebuild). Expuesto con
`SWAP_LINUX_HEADERS=1`; ensamblado end-to-end en host OK. Falta sólo la corrida in-VM para el sello.
**Pieza 4 — bwrap (`recipes/bwrap.toml`, bubblewrap 0.11.0, validada en host):** EL sandbox del propio
lab (`hammer-build/src/sandbox.rs`). Binario estático ⇒ swap de archivo sobre `/toolchain/usr/bin/bwrap`.
lab (`takana-build/src/sandbox.rs`). Binario estático ⇒ swap de archivo sobre `/toolchain/usr/bin/bwrap`.
Como tool del toolchain (no input del 4/4) NO necesita casar byte-a-byte con Alpine, sólo aislar igual.
Tres sub-hallazgos que la hicieron más jugosa que make/busybox: (1) el toolchain Alpine no trae meson/
ninja/python — bypaseamos meson compilando los 4 `.c` de bubblewrap directo con `zig cc` (+`config.h`
@@ -282,14 +282,14 @@ Diseño completo en [SDD 11 — Bootstrap from-scratch](11-bootstrap.md). Resume
agregó **materialización de build-deps** al lab: `deps.build` ahora se construye recursivamente y cada
artefacto sellado se apila como capa `--overlay-src` BAJO el rootfs del sandbox, dejando sus
`usr/{include,lib,lib/pkgconfig}` en `/usr` — pkgconf y zig cc los hallan sin plumbing de flags (recetas
sin deps quedan byte-iguales, baseline intacto). Validación host fuerte: hammer-bwrap es estático, corre
sin deps quedan byte-iguales, baseline intacto). Validación host fuerte: takana-bwrap es estático, corre
`--version`/sandboxea, y **musl rebuildeó byte-idéntico** usándolo de sandbox (bisección). Expuesto con
`SWAP_BWRAP=1`; falta sólo correrla in-VM.
**Pieza 5 — coreutils (`recipes/coreutils.toml`, GNU 9.8, validada en host):** cp/mkdir/ln/chmod/mv/
install que el `make install` de musl/busybox usa. Empaquetada multicall (`--enable-single-binary`,
igual layout que Alpine: un `/bin/coreutils` + ~100 symlinks), así **un solo swap del binario** rutea
todos los applets. Tool, no input ⇒ build vainilla sin patches. Bisección host fuerte: **musl Y busybox
rebuildearon byte-idéntico** (`57b66a2e`, `56664d70`) con hammer-coreutils pisando el de Alpine.
rebuildearon byte-idéntico** (`57b66a2e`, `56664d70`) con takana-coreutils pisando el de Alpine.
Expuesto con `SWAP_COREUTILS=1`. Falta correrla in-VM.
**Herramientas de toolchain extra desde fuente (provenance, no en el camino del 4/4 mínimo):**
`recipes/{patch,m4,pkgconf}.toml` — GNU patch 2.8 (lo usa `apply_patches`), GNU m4 1.4.20 (base
@@ -319,18 +319,18 @@ Diseño completo en [SDD 11 — Bootstrap from-scratch](11-bootstrap.md). Resume
(binutils/pkgconf) quedan salteadas por diseño. Capa de arranque restante (status libc, no vale
de-Alpinizar): gcc, m4, libz/libzstd runtime.
- ✅✅✅ **CERRADO — kernel-from-source ([SDD 11](11-bootstrap.md), último eslabón de soberanía):** el
kernel Linux 6.16.12 construido por hammer (`recipes/linux.toml`, `CC=gcc`, monolítico
kernel Linux 6.16.12 construido por takana (`recipes/linux.toml`, `CC=gcc`, monolítico
e1000/overlay/userns=y) **BOOTEA la VM del selfhost-verify** y el rebuild in-VM da `✓ REPRODUCIBLE`
bit a bit (`HAMMER_KERNEL=1 KERNEL=<bzImage> KVM=1 MEM=16384 PRESEED=hammerd`). Config lean (build
bit a bit (`TAKANA_KERNEL=1 KERNEL=<bzImage> KVM=1 MEM=16384 PRESEED=hammerd`). Config lean (build
~35→~14 min, bzImage 13→8.7 MB). El muro `pivot_root` (`/` = rootfs absoluto del mount-ns ⇒ bwrap no
pivota; quirk que el kernel host enmascaraba) se resolvió con un `/init` wrapper `switch_root` a tmpfs
(condicional a `HAMMER_KERNEL=1`, no toca el camino del kernel host). **Build-deps del kernel TODOS
(condicional a `TAKANA_KERNEL=1`, no toca el camino del kernel host). **Build-deps del kernel TODOS
de-Alpinizados:** `recipes/{flex,bison}.toml` (kconfig), `recipes/openssl.toml` (3.5.4, certs/extract-cert),
`recipes/elfutils.toml` (sólo libelf 0.194 para objtool, con shims musl mínimos). Capa de arranque
restante: m4, libz/libzstd, gcc (status libc).
-**Etapa A — orquestador `bootstrap all` + log de transparencia exportable:** `hammer_bootstrap::all`
encadena stage0→stage1→stage2 en una corrida del host (`hammer bootstrap all --url … --sha256 … --version …`)
y deja el `bootstrap.json` poblado; `hammer bootstrap manifest` lo imprime (la superficie de publicación
encadena stage0→stage1→stage2 en una corrida del host (`takana bootstrap all --url … --sha256 … --version …`)
y deja el `bootstrap.json` poblado; `takana bootstrap manifest` lo imprime (la superficie de publicación
del log, SDD 11 §4). El veredicto `✓ REPRODUCIBLE` lo sigue sellando el rebuild in-VM (`selfhost-verify.sh`),
no el host — `all` ancla la referencia para que ese rebuild la compare. **Pendiente de la Etapa A:** el
smoke end-to-end de I3 (bus único) contra un init **vivo** (arje+hammerd corriendo) — no reproducible
@@ -339,11 +339,11 @@ Diseño completo en [SDD 11 — Bootstrap from-scratch](11-bootstrap.md). Resume
bus de agente ya tiene fuente real. **Fuente (arje):** `arje-bus` ganó `BusRequest::Subscribe`
+ `BusPayload::Event(BusEvent)`; arje-zero difunde en `on_death` `EnteCrashed{id,label,status}`
/ `EnteRestarting{delay_ms}` / `EnteExited` a las conexiones suscritas, purgando las muertas
(4 tests). **Sink (hammer):** `hammerd::crashes` traduce la señal normalizada → `Event::Crashed`
`EventBus``/run/agent.sock` (3 tests). **Transporte (hammer):** `hammerd::arje_link` se
(4 tests). **Sink (takana):** `hammerd::crashes` traduce la señal normalizada → `Event::Crashed`
`EventBus``/run/agent.sock` (3 tests). **Transporte (takana):** `hammerd::arje_link` se
suscribe al bus de arje (`$ENTE_BUS_SOCK`) y reenvía cada `BusEvent`. Decisión de wire =
**relectura del frame postcard** (mirror mínimo del subconjunto de suscriptor; no arrastra el
crate-graph de arje ⇒ hammer sigue standalone). El layout está **verificado byte-a-byte contra
crate-graph de arje ⇒ takana sigue standalone). El layout está **verificado byte-a-byte contra
`arje-bus` real** (mismas versiones ulid 1.2 / postcard 1.1; frame Subscribe = `[00,01,00,0d]`)
y cubierto por un round-trip local (2 tests). Resta sólo el smoke end-to-end contra un init
vivo (no reproducible sin arje corriendo).
+33 -33
View File
@@ -1,19 +1,19 @@
# SDD 11 — Bootstrap from-scratch (track posterior)
> **Estado:** diseño. Abre el *track posterior* del [SDD 10](10-roadmap.md): bajar de "hammer
> sobre Alpine" a "hammer sobre sí mismo". No bloquea ninguna Fase 06 (ya cerradas); reusa
> **Estado:** diseño. Abre el *track posterior* del [SDD 10](10-roadmap.md): bajar de "takana
> sobre Alpine" a "takana sobre sí mismo". No bloquea ninguna Fase 06 (ya cerradas); reusa
> el laboratorio de build ([SDD 02](02-build-lab.md)) sin retrabajo.
## 1. El problema: el cordón umbilical
Hoy el userland que `hammer` muta es el de Alpine (musl + busybox ya cocidos). Eso fue
Hoy el userland que `takana` muta es el de Alpine (musl + busybox ya cocidos). Eso fue
deliberado ([ADR 0002](adr/0002-alpine-first.md)): validar la capa AI-nativa antes de gastar
meses en un bootstrap. Validada esa capa, el track posterior responde una sola pregunta:
> **¿Puede `hammer` compilar el sistema completo que ejecuta, sin pedirle nada al host más
> **¿Puede `takana` compilar el sistema completo que ejecuta, sin pedirle nada al host más
> que un toolchain semilla fijado por hash?**
Si la respuesta es sí *y* el resultado es bit-reproducible, hammer deja de heredar la
Si la respuesta es sí *y* el resultado es bit-reproducible, takana deja de heredar la
confianza de Alpine y la deriva de su propio [log de transparencia](09-trust-model.md).
## 2. Principios (heredados, no nuevos)
@@ -54,7 +54,7 @@ confianza de Alpine y la deriva de su propio [log de transparencia](09-trust-mod
Modelamos el toolchain semilla como una **fuente fijada**, no como un build: un `Source` de
tipo tarball con `sha256` pinned (`zig-<ver>-linux-x86_64.tar.xz`, o el tarball de
`musl-cross-make`). `hammer-bootstrap` lo descarga (fuera del sandbox, como `hammer_build::download`
`musl-cross-make`). `takana-bootstrap` lo descarga (fuera del sandbox, como `hammer_build::download`
ya hace para `patch_url`/`content_url`), verifica el sha256 **antes** de sellarlo, y lo deposita
en el store con un `artifact_hash` derivado de ese sha256. A partir de ahí el resto del
bootstrap depende del *hash del store*, no de la ruta del host.
@@ -105,22 +105,22 @@ Cada etapa anota una línea `(stage, recipe_hash, artifact_hash, seed_hash, ts)`
transparencia ([SDD 09 §4](09-trust-model.md)): un tercero reproduce el bootstrap y verifica que
sus hashes coinciden con los publicados, sin confiar en el binario del autor.
Implementado en `hammer-bootstrap::manifest` (`BootstrapManifest` / `StageEntry`): append-only e
Implementado en `takana-bootstrap::manifest` (`BootstrapManifest` / `StageEntry`): append-only e
**idempotente** (de-dup por `(stage, artifact_hash)`), escritura atómica, y `ts` que **no** entra
en ningún hash (dos reproducciones del mismo bootstrap difieren a lo sumo en ese campo). Stage 0
ya anota su línea: `recipe_hash = None` (la semilla se ingiere, no se compila) y
`artifact_hash == seed_hash` (la semilla es a la vez insumo y producto). **`hammer bootstrap
`artifact_hash == seed_hash` (la semilla es a la vez insumo y producto). **`takana bootstrap
manifest`** imprime el `bootstrap.json` actual: es la superficie de **publicación** del log: el autor
lo comparte y un tercero reproduce el bootstrap y compara sus hashes contra estas líneas, sin confiar
en el binario del autor ([SDD 09 §4](09-trust-model.md)).
## 5. Interfaz
Nuevo crate `hammer-bootstrap` (orquestador) sobre `hammer-build` + `hammer-core::Store`.
Nuevo crate `takana-bootstrap` (orquestador) sobre `takana-build` + `takana-core::Store`.
No introduce mecanismo nuevo de build: encadena recetas y persiste el manifiesto.
```rust
// hammer-bootstrap
// takana-bootstrap
pub fn stage0(seed: &SeedSpec, store: &Store) -> Result<ArtifactHash>; // ✅ toolchain semilla
pub fn stage1(spec: &Stage1Spec, cfg: &BuildConfig, store: &Store)
-> Result<RootfsHash>; // ◑ musl+busybox+hammerd; init arje ☐
@@ -164,11 +164,11 @@ cualquier espejo del mismo tarball produce el mismo artefacto.
CLI (implementado lo de Stage 0; el resto pendiente):
```
hammer bootstrap stage0 --url URL --sha256 HEX --version V [--seed zig|musl-cross-make] # ✅
hammer bootstrap stage1 --seed-hash HASH [--seed zig] [--recipes DIR] # ✅ booteado en QEMU
hammer bootstrap stage2 --rootfs HASH [--verify CONTENT_HASH] # ✅ ancla+verifica; rebuild en VM ✓ REPRODUCIBLE
hammer bootstrap all --url URL --sha256 HEX --version V [--seed zig] [--recipes DIR] # ✅ las tres etapas + manifiesto
hammer bootstrap manifest # imprime el bootstrap.json (log de transparencia, §4) # ✅
takana bootstrap stage0 --url URL --sha256 HEX --version V [--seed zig|musl-cross-make] # ✅
takana bootstrap stage1 --seed-hash HASH [--seed zig] [--recipes DIR] # ✅ booteado en QEMU
takana bootstrap stage2 --rootfs HASH [--verify CONTENT_HASH] # ✅ ancla+verifica; rebuild en VM ✓ REPRODUCIBLE
takana bootstrap all --url URL --sha256 HEX --version V [--seed zig] [--recipes DIR] # ✅ las tres etapas + manifiesto
takana bootstrap manifest # imprime el bootstrap.json (log de transparencia, §4) # ✅
```
## 6. Decisiones (fijadas en [ADR 0008](adr/0008-bootstrap-stages.md))
@@ -205,19 +205,19 @@ resolver para llegar ahí:
### 7.1 Qué necesita el builder rootfs
Para correr `hammer bootstrap stage1` **dentro** de sí mismo: el binario `hammer` (estático), las
Para correr `takana bootstrap stage1` **dentro** de sí mismo: el binario `takana` (estático), las
recetas, la **semilla** (`zig`, ya un artefacto sellado), `make`+autotools, `cargo`+rust,
`linux-headers`, y `bwrap` (el lab anida un sandbox ⇒ userns sin privilegios dentro del chroot/VM).
### 7.2 Dos niveles de pureza (fasable)
- **(a) Builder pragmático** — el toolchain entra al rootfs **desde Alpine** (apk), no construido por
hammer. Demuestra el **mecanismo** (rebuild in-rootfs + `of_tree(stage1)==of_tree(stage1')`) y
takana. Demuestra el **mecanismo** (rebuild in-rootfs + `of_tree(stage1)==of_tree(stage1')`) y
cierra la reproducibilidad *end-to-end*, sin ser aún auto-alojamiento puro.
- **(b) Auto-alojamiento puro** — el toolchain lo **construye hammer desde fuente** (recetas para
- **(b) Auto-alojamiento puro** — el toolchain lo **construye takana desde fuente** (recetas para
make/autotools, y el gran tramo: rust/llvm o un rustc bootstrappeado). Es el end-state; el más
caro (construir rust desde cero). Se llega **incrementalmente**, reemplazando una a una las piezas
Alpine por componentes hammer, con Stage 2 verificando cada paso.
Alpine por componentes takana, con Stage 2 verificando cada paso.
- **Pieza 1 — GNU make `4.4.1`** (`recipes/make.toml`): arranque de (b). Build estático musl con
`zig cc` (mismo camino que `grep`: tarball release con `configure` → AutoconfReady), sellado en
`b3:fbad44ac…` y **reproducible bit-a-bit** (dos builds en stores distintos ⇒ árbol idéntico).
@@ -225,19 +225,19 @@ recetas, la **semilla** (`zig`, ya un artefacto sellado), `make`+autotools, `car
(`ToolchainSwap { name, artifact, rel_path }`) monta el binario sellado sobre el path Alpine en
`/toolchain` (estático musl ⇒ sin shim del loader) y lo ancla en el **hash lógico** del builder
(`swaps_digest`, ordenado por nombre): la procedencia deja de ser "todo Alpine" y se vuelve
auditable para el log de transparencia. CLI: `hammer bootstrap builder --swap make=<hash>[:rel]`
auditable para el log de transparencia. CLI: `takana bootstrap builder --swap make=<hash>[:rel]`
(repetible; `rel_path` por defecto `usr/bin/<name>`). En el host: pura `b3:8a370f5d…` vs swapped
`b3:e7e2282c…`, y `/toolchain/usr/bin/make` queda hardlinkeado al artefacto `fbad44ac…`. El
`selfhost-verify.sh` lo expone opt-in con `SWAP_MAKE=1` (construye make y lo swapea). **Validado en
host:** con make de hammer los 4/4 reproducen `of_tree=9adefb82…` (idéntico al baseline determinista).
host:** con make de takana los 4/4 reproducen `of_tree=9adefb82…` (idéntico al baseline determinista).
- **Pieza 2 — busybox `1.36.1`** (ya es componente de Stage 1, `recipes/busybox.toml`): provee el
`sh`+coreutils que el build usa del `/toolchain`. El `/toolchain` de Alpine es **mixto** — busybox
para `sh/sed/grep/awk/tar/find` (symlinks → `/bin/busybox`) y GNU coreutils para `cp/mkdir/install`.
El swap monta el busybox estático de hammer sobre `/toolchain/bin/busybox` (`--swap
El swap monta el busybox estático de takana sobre `/toolchain/bin/busybox` (`--swap
busybox=<hash>:bin/busybox`; sin código nuevo — el mecanismo `--swap` ya soporta `rel_path`): los
applets busybox-backed pasan a usar hammer-busybox, coreutils GNU intacto. `SWAP_BUSYBOX=1` en el
verify. **Validado en host:** con make+busybox de hammer los 4/4 (incl. busybox compilándose a sí
mismo con hammer-busybox de shell) reproducen `of_tree=9adefb82…`.
applets busybox-backed pasan a usar takana-busybox, coreutils GNU intacto. `SWAP_BUSYBOX=1` en el
verify. **Validado en host:** con make+busybox de takana los 4/4 (incl. busybox compilándose a sí
mismo con takana-busybox de shell) reproducen `of_tree=9adefb82…`.
- **Determinismo (transversal):** `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` en el sandbox
serializa el jobserver ⇒ reproducible e independiente del nº de CPUs (SDD 09 §2). El baseline
@@ -248,7 +248,7 @@ recetas, la **semilla** (`zig`, ya un artefacto sellado), `make`+autotools, `car
reusado; ver [`scripts/rust-frontier/`](../scripts/rust-frontier/README.md)). El **capstone** corrió
los 6 swaps juntos in-VM (`SWAP_{MAKE,BUSYBOX,COREUTILS,BWRAP,LINUX_HEADERS,RUST}=1`) dando
`✓ REPRODUCIBLE` con `of_tree(stage1')=7fa6cb4e…` bit a bit. Dos anclajes de `of_tree`: `9adefb82…`
(rustc Alpine, los 5 tools lo mantienen) y `7fa6cb4e…` (hammer-rust, auto-consistente — el
(rustc Alpine, los 5 tools lo mantienen) y `7fa6cb4e…` (takana-rust, auto-consistente — el
compilador emite los bytes). binutils desde fuente (`recipes/binutils.toml`, 2.45.1) cierra la
provenance pero es **inerte** al `of_tree` (zig provee as/ld; verificado por bisección). No quedan
piezas del toolchain por de-Alpinizar.
@@ -265,17 +265,17 @@ el toolchain) y **correr el rebuild anidado** en la VM.
El **ensamblado** del builder está implementado (`hammer_bootstrap::builder_rootfs`, variante a):
```
hammer bootstrap builder --stage1 <H> --seed-hash <H> \
takana bootstrap builder --stage1 <H> --seed-hash <H> \
[--toolchain .dev-fs/alpine] [--toolchain-tag alpine-3.23.4] \
[--hammer-bin <static-musl-hammer>] [--ref-content of_tree(stage1)] \
[--work-cache work/] [--out work/builder-rootfs]
```
Sobre el rootfs de Stage 1 (hidratado como base) monta: `/toolchain` (el rootfs Alpine, el sandbox
de build), `/store` con la **semilla replicada** (el `hammer` de adentro resuelve zig por hash),
de build), `/store` con la **semilla replicada** (el `takana` de adentro resuelve zig por hash),
`/usr/bin/hammer` + `/etc/hammer/recipes`, y el driver **`/usr/bin/rebuild-stage1`** que lee
`/etc/hammer/rebuild.env` (`SEED_HASH`/`SEED_KIND`/`REF_CONTENT`), apunta `HAMMER_ROOTFS=/toolchain`,
corre `hammer bootstrap stage1` y compara `stage1'` contra la referencia con `stage2 --verify`.
`/etc/hammer/rebuild.env` (`SEED_HASH`/`SEED_KIND`/`REF_CONTENT`), apunta `TAKANA_ROOTFS=/toolchain`,
corre `takana bootstrap stage1` y compara `stage1'` contra la referencia con `stage2 --verify`.
El builder **no se sella** (su `/toolchain` Alpine no es content-addressed); se ensambla en un
`out_dir` para empaquetar como initramfs. Su identidad sí es reproducible: un `ArtifactHash` *lógico*
@@ -285,19 +285,19 @@ manifiesto (línea stage 2). Validado contra el store real (Stage 1 `73d7a9be…
`scripts/selfhost-verify.sh` empaqueta el builder, lo bootea con KVM y corre `rebuild-stage1`
no-interactivo; in-VM reconstruyó los 4/4 (arje-zero cu=1 en ~27 min) y `of_tree(stage1') =
b3:0039b2b9…` igualó la referencia ⇒ `✓ REPRODUCIBLE: stage1' == stage1`. Lo que queda es la
variante (b), el auto-alojamiento *puro* (toolchain construido por hammer desde fuente, §7.2).
variante (b), el auto-alojamiento *puro* (toolchain construido por takana desde fuente, §7.2).
**Arrancada:** la pieza 1, GNU make `4.4.1` (`recipes/make.toml`), ya se construye desde fuente
con el lab — estática, reproducible bit-a-bit (§7.2b). Ver [runbook §8](runbooks/stage1-vm-boot.md).
## 8. Hecho cuando
`hammer bootstrap --all` produce un rootfs que, ejecutado en la VM destino, **reconstruye su
`takana bootstrap --all` produce un rootfs que, ejecutado en la VM destino, **reconstruye su
propio toolchain y userland con hashes idénticos a los publicados**, sin que ninguna
herramienta del host entre al resultado. Ese día Alpine deja de ser una dependencia y pasa a
ser, a lo sumo, una conveniencia de desarrollo.
**Estado (2026-06-12):** el **mecanismo** está demostrado end-to-end con la variante (a) — el
rootfs reconstruye su userland in-VM con `of_tree` idéntico (`✓ REPRODUCIBLE`, §7.4). Falta sólo
la variante (b): que el **toolchain** dentro del builder lo construya hammer desde fuente (no
la variante (b): que el **toolchain** dentro del builder lo construya takana desde fuente (no
provenga de Alpine), reemplazando una a una las piezas con Stage 2 verificando cada paso. Ese es
el corte pleno del cordón.
+8 -8
View File
@@ -1,6 +1,6 @@
# SDD 12 — `arje` como init real del Stage 1
> **Estado:** diseño (2026-06-11). Contraparte en hammer del plan de tawasuyu
> **Estado:** diseño (2026-06-11). Contraparte en takana del plan de tawasuyu
> [`03_ukupacha/arje/PLAN-ATESTACION-Y-HAMMER.md`](https://gitea.gioser.net/sergio/hammer) §B.
> Cierra el último ítem ◑ del [SDD 11](11-bootstrap.md): reemplazar el init provisional de
> busybox por **arje-zero como PID 1** del rootfs de Stage 1, lo que entrega el **`CRASHED`
@@ -51,7 +51,7 @@ Fuente: `tawasuyu/03_ukupacha/arje/init/arje-zero/`. Secuencia (PID 1):
## 3. Qué debe proveer el rootfs de Stage 1
Derivado del contrato §2. El ensamblado (`hammer-bootstrap::assemble_rootfs`) debe garantizar:
Derivado del contrato §2. El ensamblado (`takana-bootstrap::assemble_rootfs`) debe garantizar:
| Necesidad | Detalle | Quién lo pone |
|---|---|---|
@@ -76,7 +76,7 @@ viable para Stage 1:
{
"schema_version": 1,
"id": "<ULID estable de la seed de Stage 1>",
"label": "hammer-stage1",
"label": "takana-stage1",
"provides": ["Spawn", "Journal"],
"payload": "Virtual",
"supervision": "OneShot",
@@ -113,8 +113,8 @@ viable para Stage 1:
| Opción | Cómo | Trade-off |
|---|---|---|
| **A. Template JSON** (recomendada v1) | `assemble_rootfs` escribe el JSON de arriba desde un template versionado, fijado al mismo commit de arje-zero | hammer queda **autocontenido** (no depende de card-core como librería); riesgo de drift de schema, mitigado por pin + smoke test de boot en VM |
| **B. Dep `card-core`** | hammer-bootstrap añade git-dep a `card-core` y construye el `Card` con tipos + `to_json_pretty` | type-safe, sin drift; **acopla el build de hammer al de tawasuyu** (contra el espíritu de C.3 del plan: tawasuyu es *fuente de recetas*, no librería de hammer) |
| **A. Template JSON** (recomendada v1) | `assemble_rootfs` escribe el JSON de arriba desde un template versionado, fijado al mismo commit de arje-zero | takana queda **autocontenido** (no depende de card-core como librería); riesgo de drift de schema, mitigado por pin + smoke test de boot en VM |
| **B. Dep `card-core`** | takana-bootstrap añade git-dep a `card-core` y construye el `Card` con tipos + `to_json_pretty` | type-safe, sin drift; **acopla el build de takana al de tawasuyu** (contra el espíritu de C.3 del plan: tawasuyu es *fuente de recetas*, no librería de takana) |
| **C. `arje-packager`** (end-state) | un paso del bootstrap construye y corre `arje-packager` (receta Cargo) para emitir —y en A1, **firmar**— la seed | canónico y necesario para la atestación A1; el más pesado |
**Recomendación:** **A** ahora (autocontenido, pin + validación en VM), migrar a **C** cuando A1
@@ -143,12 +143,12 @@ descarta: acoplar los grafos de build de los dos repos es justo lo que C.3 evita
el [runbook de boot en QEMU](runbooks/stage1-vm-boot.md); los unit tests cubren la seed y el
ensamblado, como el resto de Stage 1.
- **I3 — bus único (plan B.2).** hammerd `announce` en `arje-bus` (cambio en hammerd) y **expone el
`CRASHED` a la capa de IA** de hammer (`agent.sock` emite `{"t":"crashed",…}`). El `agent.sock`
`CRASHED` a la capa de IA** de takana (`agent.sock` emite `{"t":"crashed",…}`). El `agent.sock`
(JSON, API de IA) queda **encima**; `arje-bus` (postcard) es el plano de control del init.
- **I4 — overlay + atestación.** arje monta el overlay del store (lowerdir RO `/store` / upperdir,
plan B.1); A1/A2: la seed gana `attest: Vec<ConcesionCapacidad>` y arje-zero verifica los binarios
(BLAKE3, ya alineado por A0) **antes** de incarnar — el nexo del modelo de confianza en capas
(el `expected_hash` de un `.swm` de hammer **es** el BLAKE3 que arje atesta).
(el `expected_hash` de un `.swm` de takana **es** el BLAKE3 que arje atesta).
## 7. Decisiones (fijadas en este SDD)
@@ -165,6 +165,6 @@ descarta: acoplar los grafos de build de los dos repos es justo lo que C.3 evita
## 8. Hecho cuando
`hammer bootstrap stage1` produce un rootfs cuyo PID 1 es arje-zero, que monta los pseudo-FS, carga
`takana bootstrap stage1` produce un rootfs cuyo PID 1 es arje-zero, que monta los pseudo-FS, carga
la seed, levanta hammerd y la getty bajo supervisión real, y al matar hammerd lo reinicia con
backoff registrando el `on_death` — el `CRASHED` real que la Fase 5 difirió, ahora en el init propio.
+14 -14
View File
@@ -31,9 +31,9 @@ auto-alojamiento, pero un disco así no arranca en hardware real ni en una VM "n
```
/dev/vda1 BIOS boot (ef02, ~2 MiB, sin fs) core.img de GRUB
/dev/vda2 / ext4 [hammer-root] + /boot/bzImage + /boot/grub
/dev/vda3 /store ext4 [hammer-store] artefactos CAS (inmutable)
/dev/vda4 /var/lib/hammer ext4 [hammer-state] estado mutable (journal, overlays)
/dev/vda2 / ext4 [takana-root] + /boot/bzImage + /boot/grub
/dev/vda3 /store ext4 [takana-store] artefactos CAS (inmutable)
/dev/vda4 /var/lib/hammer ext4 [takana-state] estado mutable (journal, overlays)
```
Cadena de arranque: SeaBIOS → MBR (boot.img) → core.img (BIOS boot part) → lee `(hd0,gpt2)/boot/grub/
@@ -50,9 +50,9 @@ kernel en `/boot`, los módulos GRUB en `/boot/grub/i386-pc` y los pasos `grub-m
- **E1 — imagen auto-booteable (GRUB BIOS): ✅ primer corte** (`scripts/install-image.sh`). Bootea en
QEMU sin `-kernel` hasta la shell de arje-zero. Hoy empaqueta el *builder rootfs* (con toolchain);
un *product rootfs* lean es refinamiento.
- **E2 — instalador a disco físico:** un `hammer install <device>` que particiona un disco real y
- **E2 — instalador a disco físico:** un `takana install <device>` que particiona un disco real y
vuelca las tres particiones + GRUB (la misma lógica, apuntando a `/dev/sdX` en vez de un fichero).
- **E3 — mirror del store: ✅ primer corte** (crate `hammer-mirror` + CLI `hammer mirror push|pull|status`).
- **E3 — mirror del store: ✅ primer corte** (crate `takana-mirror` + CLI `takana mirror push|pull|status`).
Replica `/store` content-addressed (BLAKE3) entre máquinas; el hash ES la dirección, así que un mirror es
un CAS replicado + resolución por hash. La integridad se ancla en `of_tree` (content-hash del árbol): el
receptor **recomputa** `of_tree` sobre lo recibido y exige que case el del índice **antes** de sellar
@@ -63,7 +63,7 @@ kernel en `/boot`, los módulos GRUB en `/boot/grub/i386-pc` y los pasos `grub-m
Endurecimiento pendiente: anclar el `of_tree` a una raíz firmada (log de transparencia `bootstrap.json` /
atestación de Etapa D) para defenderse de un origen plenamente malicioso (hoy el invariante forzado es
"el contenido recibido hashea a lo que el índice anuncia", que ataja corrupción de transporte).
- **E4 — upgrades: ✅ primer corte** (crate `hammer-upgrade` + CLI `hammer upgrade apply|rollback|status`).
- **E4 — upgrades: ✅ primer corte** (crate `takana-upgrade` + CLI `takana upgrade apply|rollback|status`).
Aplica un árbol Stage1/producto **nuevo** (artefacto sellado del store, CAS BLAKE3) sobre el FHS vivo
sin reinstalar, dejando una **generación**, y revierte exactamente al árbol anterior con `rollback`.
**Modelo de generaciones** (inspirado en NixOS, in-place al FHS porque el boot lee el root directo, no
@@ -79,11 +79,11 @@ kernel en `/boot`, los módulos GRUB en `/boot/grub/i386-pc` y los pasos `grub-m
`HammerHydrate{artifact=tree_dir}` (replay-able). Verificación opcional de integridad: si el llamador
anuncia el `of_tree` esperado (de un índice de **mirror E3** o release firmada), el apply lo exige antes
de tocar el root. Maneja ficheros regulares + symlinks; valida E2E en host con `scripts/upgrade-e2e-test.sh`
(apply v1→v2→rollback→rollback contra el binario real). **GC de generaciones ✅** (`hammer upgrade
(apply v1→v2→rollback→rollback contra el binario real). **GC de generaciones ✅** (`takana upgrade
prune [--keep N]` / `hammer_upgrade::prune`): borra las generaciones que el rollback ya no alcanza
(huérfanas tras un rollback — las de la [cadena viva](#) siempre se conservan); `--keep N` además
recorta la cadena a sus N más nuevas (limita la profundidad de rollback, como `delete-generations` en
NixOS). **Journal de intención + replay ✅** (`hammer upgrade recover [--rollback]` /
NixOS). **Journal de intención + replay ✅** (`takana upgrade recover [--rollback]` /
`hammer_upgrade::{pending,recover}`): el apply escribe el **plan completo** a `pending.json` ANTES de
proyectar y lo limpia al commitear; si un corte/reinicio lo interrumpe, `pending.json` sobrevive y el
FHS quedó a medias. La proyección es **re-entrante** (`project_plan`, backups *idempotentes*
@@ -95,7 +95,7 @@ kernel en `/boot`, los módulos GRUB en `/boot/grub/i386-pc` y los pasos `grub-m
- **Auto-recover al arranque ✅** (`crates/hammer-recover` — mini-binario static-musl). El modelo de
generaciones es **in-place** (el FHS es la generación viva), así que NO hay menú de generaciones tipo
NixOS que ofrecer en GRUB; lo que encaja es **auto-sanar** un upgrade interrumpido al boot. El producto
no lleva el CLI `hammer` completo ⇒ `hammer-recover` es lo único que el sistema instalado necesita: el
no lleva el CLI `takana` completo ⇒ `hammer-recover` es lo único que el sistema instalado necesita: el
wrapper `/sbin/init` (de `hammer-live-install.sh`) lo corre **tras montar `/store` y `/var/lib/hammer`**
y **antes** de incarnar arje-zero. Sin `pending.json` es no-op; con uno, **completa** (roll-forward) o,
si no puede, **deshace** (roll-back) ⇒ el FHS siempre arranca consistente. Nunca aborta el boot. El
@@ -105,8 +105,8 @@ kernel en `/boot`, los módulos GRUB en `/boot/grub/i386-pc` y los pasos `grub-m
- **E5 — ISO/medio de arranque: ✅ primer corte** (`recipes/xorriso.toml` + `scripts/iso-image.sh` +
`scripts/iso-boot-test.sh`). Medio **live** que arranca ENTERO desde RAM: GRUB (El Torito) carga
kernel + initramfs desde el ISO 9660, el kernel desempaqueta el rootfs del producto a un tmpfs y corre
`/init` — sin tocar disco. Es el medio para correr el instalador (`hammer install /dev/sdX`) en
hardware nuevo o probar el sistema sin instalarlo. **Herramental construido por hammer:** el host no
`/init` — sin tocar disco. Es el medio para correr el instalador (`takana install /dev/sdX`) en
hardware nuevo o probar el sistema sin instalarlo. **Herramental construido por takana:** el host no
trae `xorriso` (y `grub-mkrescue` lo exige) ⇒ `recipes/xorriso.toml` lo construye desde fuente (GNU
xorriso 1.5.8 = libburn+libisofs+libisoburn, zig 0.13.0, estático musl, libs opcionales off ⇒ binario
autocontenido). `iso-image.sh` lo **dogfooda**: arma el ISO con el xorriso del store. **Cadena de
@@ -116,7 +116,7 @@ kernel en `/boot`, los módulos GRUB en `/boot/grub/i386-pc` y los pasos `grub-m
⇒ ISO **isohybrid** (booteable como CD *y* USB/disco). El initramfs es el `product-rootfs` lean
empaquetado como `cpio.gz` (live de RAM; la variante con squashfs+overlay para rootfs grandes es
refinamiento). Validado E2E in-VM (`iso-boot-test.sh`, ISO 113M): SeaBIOS → GRUB 2.14 (El Torito) →
Linux 6.16.12 hammer-built → `/init` (marcador `HAMMER-ISO-LIVE-OK`) → arje-zero PID1 → hammerd +
Linux 6.16.12 takana-built → `/init` (marcador `HAMMER-ISO-LIVE-OK`) → arje-zero PID1 → hammerd +
netup (lease DHCP) → **sshd escuchando**: el producto entero corriendo desde el medio live. **Pendiente
(refinamiento):** squashfs+overlay para no cargar todo el rootfs en RAM (diferido: el kernel no trae
SQUASHFS ni CDROM/SR ⇒ exige rebuild de kernel, valor marginal en el producto lean).
@@ -131,13 +131,13 @@ kernel en `/boot`, los módulos GRUB en `/boot/grub/i386-pc` y los pasos `grub-m
que la firmware no lee ("failed to load … Not Found") ⇒ sin `-F`, mformat auto-elige FAT12/16.
Validado E2E: el MISMO ISO bootea por UEFI (QEMU+OVMF → grubx64.efi → "Loaded initrd from
LINUX_EFI_INITRD_MEDIA_GUID" → arje-zero → sshd) y por BIOS (SeaBIOS, El Torito). **Refinamiento #3
CERRADO.** (Falta como pulido: rama EFI del INSTALADOR a disco — GPT+ESP en `hammer-install`, hoy el
CERRADO.** (Falta como pulido: rama EFI del INSTALADOR a disco — GPT+ESP en `takana-install`, hoy el
instalado es MBR-BIOS.)
- **Instalar DESDE el live ✅** (`scripts/iso-image.sh INSTALLER=1` + `scripts/hammer-live-install.sh`
`/usr/bin/hammer-install` + `scripts/iso-install-test.sh`). Cierra el lazo **ISO live → disco
instalado → bootea solo**. El ISO instalador bundlea un *payload de disco* en el initramfs bajo
`/usr/lib/hammer/install/` (kernel + GRUB `boot.img`/`core.img` MBR-prefix `(hd0,msdos1)/boot/grub`
+ módulos, armado en build-time con `grub-mkimage`). `hammer-install <dev>` corre **dentro del live
+ módulos, armado en build-time con `grub-mkimage`). `takana-install <dev>` corre **dentro del live
como root real** (sin los rodeos rootless de E1) usando sólo lo que el producto ya trae (busybox
`fdisk`/`mke2fs`/`mount`/`dd` + uutils `cp`): particiona **MBR** (p1=/, p2=/store, p3=/var/lib/hammer),
formatea ext2 (montable ext4), copia la **propia raíz del live** (autoinstalador) excluyendo
+9 -9
View File
@@ -1,6 +1,6 @@
# SDD 14 — Stack gráfico (Mesa iris) para el escritorio tawasuyu
> **Estado:** cola importada (2026-06-27). Contraparte en hammer del salto
> **Estado:** cola importada (2026-06-27). Contraparte en takana del salto
> demo→producción de arje: el rootfs de producto necesita **Mesa** para que el
> greeter real de mirada (`mirada-greeter-llimphi`) tome el DRM tras el handoff
> del splash. Seed de producción: `tawasuyu/03_ukupacha/arje/seeds/arje-tawasuyu.card.json`
@@ -25,13 +25,13 @@ libclc, spirv-llvm-translator, rust-bindgen, Vulkan, X11/glx:
-Dplatforms=wayland -Dglx=disabled -Dexpat=enabled
```
Eso deja un Mesa que sólo arrastra deps que hammer ya tiene o son chicas.
Eso deja un Mesa que sólo arrastra deps que takana ya tiene o son chicas.
(Para AMD/NVIDIA-nouveau más adelante: `radeonsi`/`nouveau` sí piden LLVM →
otra rama del track.)
## DAG de dependencias (estado contra el corpus actual)
| Tier | Receta | meson? | Estado en hammer | Notas |
| Tier | Receta | meson? | Estado en takana | Notas |
|---|---|---|---|---|
| build | `python3` | — | 🟢 en store | meson/mako lo usan |
| build | `meson` | — | 🟡 incoming | importada de Alpine 1.11.1; pyproject, necesita python3 |
@@ -47,25 +47,25 @@ otra rama del track.)
| **top** | `mesa` | sí | 🟡 incoming | 26.1.1; recortar a iris-only (arriba); deps: libdrm, wayland, wayland-protocols, expat, zlib, zstd, python3+mako |
🟢 listo · 🟡 importada, **falta adaptar** (traducir `abuild-meson`/`_abuild_phase`
de Alpine a fases hammer + `meson`/`samurai` directos, como hizo `elfutils`) · ⚪ por crear.
de Alpine a fases takana + `meson`/`samurai` directos, como hizo `elfutils`) · ⚪ por crear.
## Trabajo de adaptación por receta (lo real)
Las recetas en `recipes/incoming/` son **puntos de partida** de `import-alpine`:
traen los parches musl de Alpine (lo valioso) pero usan los helpers de abuild que
el lab de hammer no tiene. Cada una necesita, mínimo:
el lab de takana no tiene. Cada una necesita, mínimo:
1. Reemplazar `abuild-meson` por `meson setup output --prefix=/usr --buildtype=release <-Dflags>`
y `_abuild_phase`/`DESTDIR=/out` por las fases `configure`/`compile`/`install` de hammer.
y `_abuild_phase`/`DESTDIR=/out` por las fases `configure`/`compile`/`install` de takana.
2. `compiler = "zig-cc"`, `target = "x86_64-linux-musl"`, `link = "dynamic"` (son libs compartidas).
3. Declarar las deps de build en el grafo de hammer (no como `makedepends` de Alpine).
3. Declarar las deps de build en el grafo de takana (no como `makedepends` de Alpine).
4. Verificar `zig_version` si zig 0.14 miscompila (patrón de `elfutils`/`binutils`/`flex`: fijar 0.13.0).
Orden de build (hojas→raíz): `meson`+`samurai``libdrm`,`wayland``wayland-protocols`,`seatd``mesa`.
## Estado de build (2026-06-27)
Construidas y selladas en el store (`hammer --store store build recipes/<x>.toml`):
Construidas y selladas en el store (`takana --store store build recipes/<x>.toml`):
| Receta | Estado | Nota de adaptación |
|---|---|---|
@@ -125,6 +125,6 @@ Los otros dos (arje-splash, net-bring-up) no dependen de Mesa.
## Hecho cuando
`hammer bootstrap product` produce un rootfs que, con la seed `arje-tawasuyu`,
`takana bootstrap product` produce un rootfs que, con la seed `arje-tawasuyu`,
arranca `arje-zero` → splash → `mirada-greeter` toma el DRM con Mesa/iris, sin
caer a texto. Verificación: boot en QEMU (con virgl/GPU) o metal en la laptop Iris Xe.
+28 -28
View File
@@ -1,8 +1,8 @@
# SDD 15 — Frontera AI-nativa: código por contenido, evidencia sobre confianza, cómputo como dato
> Estado: propuesta (2026-07-02). Nace de una revisión externa de las 4 piezas del stack
> (wawa · hammer/BLAKE3 · wasm determinista · hifas) que propuso seis puentes. Tres caen del
> lado de hammer/wawa y se recogen aquí; los otros tres (fuente monótona en el kernel,
> (wawa · takana/BLAKE3 · wasm determinista · hifas) que propuso seis puentes. Tres caen del
> lado de takana/wawa y se recogen aquí; los otros tres (fuente monótona en el kernel,
> fork-consistency, OS-como-CRDT) viven en `tawasuyu/03_ukupacha/PLAN-OS-CRDT.md`.
>
> **Este doc NO reemplaza nada cerrado.** Fases 06 + bootstrap Stage 02 + los 6 swaps
@@ -13,8 +13,8 @@
## 0. El principio que ya tenemos y hay que empujar
El modelo de confianza de hammer (SDD 09) es **"verificar, no confiar"**: el binario ajeno
nunca viaja, viaja la receta, y `hammer apply` **reproduce y compara** el `expected_hash`. La
El modelo de confianza de takana (SDD 09) es **"verificar, no confiar"**: el binario ajeno
nunca viaja, viaja la receta, y `takana apply` **reproduce y compara** el `expected_hash`. La
integración de IA (SDD 08 §4) es exactamente la arquitectura de frontera correcta: *la IA
propone `.swm`; el sistema reproduce; el humano hace commit; la IA nunca promueve sola.*
@@ -36,12 +36,12 @@ dura es **reproducibilidad**: dos máquinas ⇒ mismo `artifact_hash`. Eso certi
no *comportamiento*.
**El salto.** Que la IA entregue, junto al `.swm`, **evidencia de comportamiento**: property
tests, contratos, y —cuando el crate lo permita— pruebas tipo Kani/Creusot. hammer admite la
tests, contratos, y —cuando el crate lo permita— pruebas tipo Kani/Creusot. takana admite la
mutación sólo si un **checker pequeño y confiable** confirma que la evidencia pasa. Proof-carrying
code (Necula 1997), pero **el generador es un modelo**: el sistema mejora solo sin poder
corromperse solo, porque la confianza vive en el checker, no en el generador.
**Por qué encaja aquí y no en otro lado.** hammer ya tiene el sandbox hermético (bwrap) donde
**Por qué encaja aquí y no en otro lado.** takana ya tiene el sandbox hermético (bwrap) donde
correr esa evidencia de forma reproducible, ya tiene el `Orchestrator` con paso VERIFY, y ya
tiene procedencia (`.hammer/recipe.toml` sellado en el artefacto). La atestación por-hash de
arje (concesiones matcheadas por hash de binario; ver `tawasuyu` `arje/SDD`) es el mismo patrón
@@ -51,7 +51,7 @@ firmó.** H1 es esa idea, subida del boot al build.
**Tickets:**
- **H1a** ✅ — Extender el `.swm` (SDD 06) con un bloque `evidence`: lista de `{kind, cmd, expected}`
donde `kind ∈ {proptest, contract, kani, cmd-exit}`. Serialización YAML estable + `verify_schema`.
- **H1b** ✅ — El checker: `hammer swm-verify --evidence` corre cada ítem **dentro del sandbox
- **H1b** ✅ — El checker: `takana swm-verify --evidence` corre cada ítem **dentro del sandbox
reproducible** y exige `expected`. Pequeño y auditable a propósito (no un framework: un runner
de comandos con hash del output). Falla ⇒ no se propone. Reusa `Sandbox::run` + el `log_tail`
ya existente (Fase 5).
@@ -60,8 +60,8 @@ firmó.** H1 es esa idea, subida del boot al build.
no sólo "reproduce". **Frontera:** el checker confía en que las pruebas *cubren* lo que
importa — eso lo juzga el humano; H1 garantiza que *lo declarado pasa*, no que *lo declarado
basta*. Se dice así.
**Implementación:** la evidencia corre del lado del BUILD (no del agente — `hammer-agent` no
depende de `hammer-build`, separación PROPONE/CONSTRUYE). `proto::RecipeInline` lleva `evidence`
**Implementación:** la evidencia corre del lado del BUILD (no del agente — `takana-agent` no
depende de `takana-build`, separación PROPONE/CONSTRUYE). `proto::RecipeInline` lleva `evidence`
y `Event::BuildReady` lleva `verdict: Option<EvidenceVerdict>`; `hammerd::bus` la ejecuta tras
sellar el artefacto (`run_evidence` vía `swm_bridge::recipe_from_source_patch`) y adjunta el
veredicto; el `Orchestrator` lee el veredicto y, si `all_passed=false`, empuja `VerifyCheck::fail`
@@ -89,7 +89,7 @@ por anti-entropy sin coordinar).
3. **Migración de procesos como transferencia de datos** — mover un proceso = copiar `(módulo,
log de entradas)` y re-ejecutar; el estado se reconstruye determinísticamente.
**El gancho con hammer.** La caché es un **recurso acotado ruteable**: `hifas::router`
**El gancho con takana.** La caché es un **recurso acotado ruteable**: `hifas::router`
(`BudgetRouter`) reparte "créditos de cómputo" por la malla (PLAN-OS-CRDT E3c). Y la
verificación de un resultado memoizado es *reproducir la ejecución* — el mismo "verificar, no
confiar" de SDD 09, ahora sobre runtime en vez de build.
@@ -98,7 +98,7 @@ confiar" de SDD 09, ahora sobre runtime en vez de build.
`float` no determinista, sin fuentes de entropía ocultas, orden de scheduling irrelevante para
la salida. `wasmi` es determinista para el core, pero hay que **auditar y sellar** que ninguna
capacidad expuesta (tiempo, random, I/O) filtre no-determinismo a la salida hasheada. Eso es un
spike de wawa, no de hammer.
spike de wawa, no de takana.
**Tickets (spike, no fase):**
- **H2a** ✅ — Auditar determinismo del runtime wasm de wawa: enumerar toda fuente de no-determinismo
@@ -149,15 +149,15 @@ la memoización cubre la parte funcional, no el proceso entero con efectos.
## H3 — Código direccionado por contenido estilo Unison (la idea 3) — DESIGN-DOC, no sprint
**La visión.** Hoy hammer direcciona por hash el **artefacto** (binario) y la **receta**. wawa
**La visión.** Hoy takana direcciona por hash el **artefacto** (binario) y la **receta**. wawa
direcciona el **módulo wasm** y el **almacenamiento**. La idea 3 empuja al límite: direccionar
**funciones por el hash de su AST** (Unison). La IA no editaría archivos —insertaría
definiciones en una base content-addressed donde **nada se rompe por renombrar o actualizar**—
y el store de hammer, los módulos wasm de wawa y el código fuente **colapsarían en un solo
y el store de takana, los módulos wasm de wawa y el código fuente **colapsarían en un solo
espacio de nombres: el hash**. Paquete, función y proceso = el mismo tipo de objeto.
**Por qué es design-doc y no ticket.** Es **otro modelo de datos**, no una extensión. hammer y
Unison content-addressan *cosas distintas*: hammer el artefacto compilado (grano grueso,
**Por qué es design-doc y no ticket.** Es **otro modelo de datos**, no una extensión. takana y
Unison content-addressan *cosas distintas*: takana el artefacto compilado (grano grueso,
lenguaje-agnóstico, encaja con "recetas sobre fuente upstream"); Unison el AST de cada función
(grano fino, requiere un lenguaje y toolchain propios). Colapsarlos pide reescribir la unidad de
compilación entera. El valor es real (fin del dependency-hell para la IA) pero el costo es un
@@ -238,7 +238,7 @@ proyecto, no una fase.
el hash prueba** (la ingesta deriva identidades y rechaza el byte flipeado con `RefColgante`).
`wawa-verifica <paquete.wawa> <entrada> <esperado>` es el checker pequeño y auditable
(estilo H1) de la afirmación «este paquete con esta entrada produce este resultado»: exit
0/1/2 — ejecutable como evidencia `cmd-exit` en el sandbox de hammer (un `.swm` cuyo payload
0/1/2 — ejecutable como evidencia `cmd-exit` en el sandbox de takana (un `.swm` cuyo payload
es un programa wasm verificable = el puente concreto paquete↔función) o como gate de ingesta
de un peer. El transporte real (anti-entropy) sigue siendo del plan OS-CRDT.
@@ -303,7 +303,7 @@ La definición dura de compatibilidad de una config `C` contra mi estado `E`:
chequeo de slots y ser inservible por **incompleta** (paquete manipulado ⇒ `RefColgante`)
o **insegura** (resultado afirmado falso ⇒ recomputar lo rechaza) — las tres propiedades,
un solo hash.
- **H4b** ✅ — El modelo de slots **subido a la receta real de hammer** (no ya un prototipo
- **H4b** ✅ — El modelo de slots **subido a la receta real de takana** (no ya un prototipo
host). Cambios:
- `Recipe` (y el `source_patch` del `.swm`) llevan un bloque **`slots`** con `claims`/
`requires` (`slot → b3:…`), **fuera de `hash_inputs`** — misma disciplina que la evidencia
@@ -311,13 +311,13 @@ La definición dura de compatibilidad de una config `C` contra mi estado `E`:
Viaja intacto por los dos sentidos del puente (`Recipe → .swm → Recipe`, test de round-trip).
- `InstalledDb` (Etapa F) registra los `claims` por paquete y expone `system_state() →
slot → hash`: el `Estado` que el gate consulta.
- `hammer-core::compat::evaluar(estado, slots) → Veredicto` (el álgebra probada en `wawa-memo`,
ahora sobre tipos de hammer).
- **El gate en `hammer install`**: antes de reconstruir o hidratar nada, evalúa el paquete
- `takana-core::compat::evaluar(estado, slots) → Veredicto` (el álgebra probada en `wawa-memo`,
ahora sobre tipos de takana).
- **El gate en `takana install`**: antes de reconstruir o hidratar nada, evalúa el paquete
entrante contra el estado instalado. **Incompatible** (requisito sin resolver, caso wayland)
⇒ aborta con un mensaje claro; **Colisión** (caso logo) ⇒ aborta pidiendo elección, salvo
`--force-slots`; **Compatible** ⇒ procede y registra los `claims` (para que la PRÓXIMA
instalación detecte la colisión). `hammer install --help` expone `--force-slots`.
instalación detecte la colisión). `takana install --help` expone `--force-slots`.
Frontera que queda: definir el **espacio de slots** del sistema (qué es una "superficie": un
fichero, un módulo wasm de wawa, un componente) — ahí está el diseño real, no en el álgebra
(que ya está).
@@ -330,13 +330,13 @@ La definición dura de compatibilidad de una config `C` contra mi estado `E`:
detecta cuáles ya posee **otro** paquete instalado (reusa `InstalledDb.files` + el nuevo
`owner_of` — cero declaración nueva en la receta). Reinstalar el mismo paquete sobre sus
propios paths NO colisiona (es upgrade).
- `hammer install` corre este chequeo **junto** al declarado: pisar el fichero de otro
- `takana install` corre este chequeo **junto** al declarado: pisar el fichero de otro
paquete es el caso *logo* a nivel de fichero (elección) ⇒ aborta salvo `--force-slots`.
- **Verificado e2e real** (`tests/compat_gate.rs`, shell-ea al binario `hammer`): dos paquetes
- **Verificado e2e real** (`tests/compat_gate.rs`, shell-ea al binario `takana`): dos paquetes
escriben `/share/logo.png`; el segundo aborta con "COLISIÓN de fichero" **y no escribe nada**;
con `--force-slots` la elección se respeta y el fichero se escribe.
Con H4c, un paquete **sin declarar slots** ya participa del gate por lo que de verdad toca.
- **H4d** ✅ — **La BÚSQUEDA: `hammer compat <repo>`.** El `filtrar` del prototipo `wawa-memo`,
- **H4d** ✅ — **La BÚSQUEDA: `takana compat <repo>`.** El `filtrar` del prototipo `wawa-memo`,
ahora sobre el repo real y **read-only** (no construye ni toca nada). Responde la pregunta que
arrancó §H4 — *"cuando busco, ¿cuáles puedo adoptar?"*: evalúa **cada** paquete del repo contra
el estado instalado y lo particiona en **{compatibles, requieren-elección, incompatibles}**,
@@ -354,8 +354,8 @@ La definición dura de compatibilidad de una config `C` contra mi estado `E`:
- `compat::observed_requires(swm, index)` + `compat::version_conflicts(db, req)` (reusan `deps`
del `.swm`, `expected_hash` del índice y `InstalledDb.hash` — cero declaración nueva).
- Cableado en `install` (rechazo duro, no lo salva `--force-slots`: es un requisito, no una
elección) y en `hammer compat` (bucket incompatibles).
- **Verificado e2e real** (`tests/compat_gate.rs`): `hammer compat` marca `app` INCOMPATIBLE
elección) y en `takana compat` (bucket incompatibles).
- **Verificado e2e real** (`tests/compat_gate.rs`): `takana compat` marca `app` INCOMPATIBLE
porque su dep `wayland-protocol` está instalada a un hash divergido del repo — read-only, ve
el `source_patch` sin construirlo.
Con H4e, la vía observada es simétrica: **lo que el paquete escribe** (H4c) *y* **de qué depende**
@@ -420,9 +420,9 @@ H3a (design-doc) ──► registrar la visión, barato
└► H3b ✅ (experimento wasm-por-función) [sobre H2 puro: núcleo Unison verde]
└► H3c ✅ (linker de contenido: imports por hash, Merkle-DAG intra-función)
└► H4a ✅ (config = conjunto de slots por hash: compatible/completa/segura)
└► H4b ✅ (slots en la receta/.swm real + gate en `hammer install`)
└► H4b ✅ (slots en la receta/.swm real + gate en `takana install`)
└► H4c ✅ (superficies OBSERVADAS: colisión de fichero, sin declarar slots)
└► H4d ✅ (`hammer compat <repo>`: la búsqueda que particiona un repo)
└► H4d ✅ (`takana compat <repo>`: la búsqueda que particiona un repo)
└► H4e ✅ (requires OBSERVADOS: dep divergida = incompatible)
└► [proceso] replay del MonotonicLog ──► plan OS-CRDT (otro agente)
```
+35 -35
View File
@@ -1,8 +1,8 @@
# SDD 16 — harkaq: la jaula de hammer
# SDD 16 — harkaq: la jaula de takana
**Estado:** Draft 2 · **Fecha:** 2026-07-15 · **Nombre provisional:** *harkaq* ("el que detiene"; renombrable, ningún identificador depende del nombre)
> **Qué cambió del Draft 1.** El Draft 1 se escribió contra el sandbox de nix. hammer no usa nix
> **Qué cambió del Draft 1.** El Draft 1 se escribió contra el sandbox de nix. takana no usa nix
> para construir: usa bubblewrap (ADR 0004, `crates/hammer-build/src/sandbox.rs`). Eso invalidó
> por irrelevancia ~40% del documento — el argumento anti-nix, las FOD y su red abierta, y la fase
> del *fetcher mediado* que era el mayor riesgo de cronograma. Fase 0 (reconocimiento) se ejecutó
@@ -10,7 +10,7 @@
> con un beneficio que cobra antes**. Lo que sobrevive intacto es el corazón: D1 (política
> derivada), D2 (evidencia negativa) y D3 (cero quieting).
**Tesis:** hammer es una distro cuyo autor es un modelo. Su sandbox (bwrap) fue diseñado para dar
**Tesis:** takana es una distro cuyo autor es un modelo. Su sandbox (bwrap) fue diseñado para dar
*hermeticidad de facto* — aislar el build del host — no para *probar* hermeticidad ni para acotar
lo que el build usa **de adentro de la jaula**. harkaq cierra esa brecha sin pedirle política a
nadie: **la clausura de entradas declaradas de la receta ya es la política**. El subproducto es
@@ -43,8 +43,8 @@ cazando *a mano, receta por receta*, leyendo logs y adivinando. El patrón docum
`makedepends`, fase configure explícita, `.pc Requires``[deps].build`) es el parche manual de
un problema que el kernel puede diagnosticar solo.
**La inversión que hace hammer:** el sandbox de bwrap asume que quien escribe la receta sabe lo
que declaró. En hammer, quien escribe la receta es un modelo, la receta se importa
**La inversión que hace takana:** el sandbox de bwrap asume que quien escribe la receta sabe lo
que declaró. En takana, quien escribe la receta es un modelo, la receta se importa
automáticamente desde nixpkgs/Alpine, y "lo que declaró" es precisamente la parte que falla.
Nadie más tiene ese problema porque nadie más tiene una distro cuyo autor sea un modelo.
@@ -54,7 +54,7 @@ canal de evidencia.
### 0.1 El lugar exacto en el modelo de confianza
De `hammer-core/src/recipe.rs:229`, sobre la evidencia H1 (proof-carrying recipes, SDD 15):
De `takana-core/src/recipe.rs:229`, sobre la evidencia H1 (proof-carrying recipes, SDD 15):
> *"Frontera honesta: garantiza que **lo declarado pasa**, no que **lo declarado basta**."*
@@ -88,9 +88,9 @@ verificar más — que es el principio declarado de SDD 15 §0.
- No corre en wawa. Ver D6.
- No reemplaza el sandbox de bwrap; se compone encima. Defensa en capas.
- No es un contenedor de propósito general ni compite con bubblewrap/landrun.
- v1 no cubre macOS (hammer es musl/Linux).
- v1 no cubre macOS (takana es musl/Linux).
- No promete contener un exploit de kernel. Ver §8.
- **No reabre ADR 0004.** hammer no adopta nix como backend de build. Esa opción sigue diferida
- **No reabre ADR 0004.** takana no adopta nix como backend de build. Esa opción sigue diferida
y harkaq no la necesita.
---
@@ -109,7 +109,7 @@ versión del kernel miente** (RHEL 9.6 backporteó hasta ABI 5 sobre un 5.14).
| Dónde | Kernel | ABI | Veredicto |
|---|---|---|---|
| Laptop de desarrollo | 7.2.0-rc3-1-cachyos-rc | **10** | techo; todo disponible |
| Kernel de hammer | 6.16.12-metal (`recipes/linux.toml`) | **ausente** | `# CONFIG_SECURITY_LANDLOCK is not set` |
| Kernel de takana | 6.16.12-metal (`recipes/linux.toml`) | **ausente** | `# CONFIG_SECURITY_LANDLOCK is not set` |
Lo que hace falta de cada ABI, y desde dónde:
@@ -132,7 +132,7 @@ Traducción: **el mecanismo de traza que el Draft 1 iba a construir ya lo hace e
que instrumentar syscalls ni interponer un tracer. La evidencia sale del audit log del propio LSM,
que es un productor mucho más confiable que cualquier cosa en espacio de usuario.
### 3.2 El kernel de hammer: falta un flag, no una versión
### 3.2 El kernel de takana: falta un flag, no una versión
`store/060b34a6…-linux-metal/boot/config-6.16.12-metal` dice:
@@ -163,7 +163,7 @@ Ese trabajo está hecho desde siempre, por construcción:
- `--unshare-all` incluye `--unshare-net`: netns vacío para **todo** build, sin excepciones,
porque no existe la categoría "derivación que necesita red".
Eso *es* la jaula con ventanilla que D8 proponía construir. hammer nunca tuvo el agujero de las
Eso *es* la jaula con ventanilla que D8 proponía construir. takana nunca tuvo el agujero de las
FOD porque nunca tuvo FODs: separar el fetch del build fue una decisión temprana y silenciosa que
resultó ser la correcta por razones de seguridad que nadie había escrito todavía. Queda escrita acá.
@@ -189,7 +189,7 @@ de de-Alpinización de §0, y sale del kernel sin instrumentar nada.
**Precondición 1 — `audit_enabled=1`.** Medido: el laptop arranca con `audit_enabled=0` (sin
`audit=1` en el cmdline, sin `auditd`). Con audit apagado **no se emite ningún registro, de
ningún escenario**. Se enciende con `AUDIT_SET` por netlink (`CAP_AUDIT_CONTROL`) o con `audit=1`
en el cmdline — que para el kernel de hammer es horneable, igual que el resto del cmdline metal.
en el cmdline — que para el kernel de takana es horneable, igual que el resto del cmdline metal.
**Precondición 2 — `LOG_NEW_EXEC_ON` es obligatorio, y su ausencia es indetectable.** Medido:
@@ -338,7 +338,7 @@ primaria (acá) y — vía el contador — detección de pérdida.
Ley de dependencia: `harkaq-policy` no conoce el kernel (función pura de `Recipe``PolicySpec`,
testeable con `cargo test` en cualquier máquina, incluso sin Landlock). `harkaq-exec` no conoce
la receta. El acoplamiento vive sólo en `hammer-build`.
la receta. El acoplamiento vive sólo en `takana-build`.
Nota de encaje: `Sandbox::bwrap_args` ya construye la lista de deps (`self.deps`) — es
literalmente el input de `clausura()`. El `PolicySpec` se deriva de datos que la struct `Sandbox`
@@ -347,7 +347,7 @@ ya tiene en la mano.
### 4.1 La política es POR FICHERO, no por path del store (corrección medida)
El Draft 2 decía `fs_ro: [store paths de la clausura]`. **Es falso, y era otra herencia del marco
nix**, donde cada dep se ve dentro del sandbox como un `/nix/store/<hash>-x` distinto. En hammer
nix**, donde cada dep se ve dentro del sandbox como un `/nix/store/<hash>-x` distinto. En takana
no: `Sandbox::bwrap_args` apila cada dep con `--overlay-src` y las **funde en el mismo `/usr`**
que el rootfs Alpine, a propósito, para que el compilador y pkgconf las encuentren en las rutas
estándar sin plumbing de flags.
@@ -506,7 +506,7 @@ contrato.
- `c89`, `c99`, `ldd` — la misma familia que `gcc` (§4.2): sondas de compilador de Alpine. Van a
**denegación esperada**; concederlas sería dejar que `configure` compile con Alpine.
- `/usr/bin/make`**la decisión de diseño de verdad**, y no la toma harkaq. Hoy los builds de
hammer usan el `make` de Alpine sin declararlo. O es runtime base (se acepta y se escribe), o
takana usan el `make` de Alpine sin declararlo. O es runtime base (se acepta y se escribe), o
es deuda (y se declara como dep). Nótese que esto es *exactamente* lo que los swaps del
`selfhost-verify` reemplazan uno a uno: **la lista de deuda de harkaq y la lista de swaps del
bootstrap son la misma lista, descubierta por dos caminos.** Que coincidan es la mejor
@@ -520,7 +520,7 @@ contrato.
Es el corazón, y sobrevive al Draft 1 sin un rasguño. Todo sandbox del mundo muere en la
ergonomía: alguien mantiene a mano un perfil de permisos, nadie lo revisa, todos terminan en
permisivo. hammer no tiene ese problema porque **la receta ya declara sus deps por hash**.
permisivo. takana no tiene ese problema porque **la receta ya declara sus deps por hash**.
`clausura(deps)` es el conjunto exacto de rutas de solo lectura. Cero configuración humana,
precisión igual a la del direccionamiento por contenido.
@@ -541,7 +541,7 @@ Si la política concede *exactamente* la clausura declarada y el kernel no deneg
build no usó nada fuera de lo declarado. **La política es la afirmación; la ausencia de
denegaciones es la prueba.** Coste de la traza: un lector de audit y una lista casi siempre vacía.
Esto convierte la reproducibilidad de hammer, que hoy es una promesa arquitectónica verificable
Esto convierte la reproducibilidad de takana, que hoy es una promesa arquitectónica verificable
sólo rebuildeando, en un certificado por build.
### D3 — Cero quieting. Las denegaciones son el producto
@@ -604,7 +604,7 @@ Las restricciones de Landlock se heredan automáticamente por `fork()` y `exec()
falta cuando `Sandbox::run` lanza `/bin/sh -c` que lanza make que lanza `zig cc`. Con ABI 8
disponible (6.16.12 lo tiene), usar `RESTRICT_SELF_TSYNC` para cubrir todos los hilos.
### D6 — harkaq es órgano de hammer, no de la suite. Rompe la paridad wawa, y está bien
### D6 — harkaq es órgano de takana, no de la suite. Rompe la paridad wawa, y está bien
Landlock, seccomp y cgroups son Linux puro; en `x86_64-unknown-none` no existen. Esto viola el
principio de paridad Linux/wawa que gobierna el resto del ecosistema, y hay que decirlo en voz
@@ -612,7 +612,7 @@ alta antes de que alguien lo cobre.
El costo es aceptable porque **el bucle de build del agente es una actividad Linux por
definición**. wawa nunca compila nada: consume artefactos atestados del CAS. La frontera de
confianza de wawa es la atestación; la de hammer es la jaula. Son mecanismos distintos para
confianza de wawa es la atestación; la de takana es la jaula. Son mecanismos distintos para
posiciones distintas del pipeline, y eso es coherente, no inconsistente.
### D7 — Fallo cerrado, y honesto sobre el ABI
@@ -621,7 +621,7 @@ posiciones distintas del pipeline, y eso es coherente, no inconsistente.
- `abi_evidencia = V7` para emitir un `Verdict` con evidencia.
- Entre V3 y V7: el build corre, el `Verdict` sale `SinEvidencia`, iniy lo trata con
`uncertainty` alta en vez de creerle.
- Sin Landlock (el kernel de hammer **hoy**): `SinEvidencia`, y el build corre igual bajo el bwrap
- Sin Landlock (el kernel de takana **hoy**): `SinEvidencia`, y el build corre igual bajo el bwrap
de siempre. harkaq es inerte, no un muro.
Nunca se emite evidencia que el kernel no respaldó. Se consulta el ABI con el syscall, jamás
@@ -685,12 +685,12 @@ es hipotético: el banco de pruebas de Q1 ya cayó en esa trampa una vez (§3.4,
## 6. Fases
**Fase 0 — Reconocimiento.****CERRADA (2026-07-15)**. Ver §3. Salidas: ABI 10 en el laptop,
Landlock ausente-pero-a-un-flag en el kernel de hammer, cero FODs y fetcher ya construido.
Landlock ausente-pero-a-un-flag en el kernel de takana, cero FODs y fetcher ya construido.
**Fase 1 — El hito demostrable (una tarde).**
Precondiciones, todas ya medidas en §3 y ninguna especulativa:
1.**`-e SECURITY -e SECURITY_LANDLOCK -e AUDIT` en `recipes/linux.toml`** (2026-07-15). Es lo
único que faltaba para que harkaq corra **dentro** de hammer (VM/metal) y no sólo en el laptop
único que faltaba para que harkaq corra **dentro** de takana (VM/metal) y no sólo en el laptop
y la granja. `CONFIG_LSM` del defconfig ya lista `landlock` de primero ⇒ bastó encenderlo, sin
tocar la cadena de LSMs. 6.16.12 da **ABI 7** = el audit de denegaciones, que es de lo que
cuelga toda la evidencia. `AUDIT` ya venía `=y`; se fija explícito porque **sin él no se emite
@@ -700,11 +700,11 @@ Precondiciones, todas ya medidas en §3 y ninguna especulativa:
3. `LANDLOCK_RESTRICT_SELF_LOG_NEW_EXEC_ON` en el `restrict_self` (§3.4). **Sin esto no hay nada.**
4. El canario de D9 antepuesto al comando del builder.
Después: tomar **una** receta real de hammer. Derivar su política de la clausura. Correrla bajo
Después: tomar **una** receta real de takana. Derivar su política de la clausura. Correrla bajo
Landlock+seccomp dentro del bwrap actual. Emitir el `Verdict` con `denials = []` **y el canario
visto**.
**✅ HITO DE FASE 1 CUMPLIDO (2026-07-15).** `hammer build` sobre una receta **real del catálogo**
**✅ HITO DE FASE 1 CUMPLIDO (2026-07-15).** `takana build` sobre una receta **real del catálogo**
(`recipes/zlib.toml`), con la política derivada de la clausura declarada y el `Verdict` emitido
**por fase**:
@@ -735,7 +735,7 @@ impuro {"estado":"Impuro","canario_visto":true,"contador_kernel":2,
Canario visto y contador coherente en ambos ⇒ los tres estados de D9 funcionan sobre el kernel
real. Precondiciones 24 ✅. Falta: la precondición 1 (sólo para que harkaq corra *dentro* de
hammer — el hito se demuestra en el laptop, que ya está en ABI 10), `harkaq-policy` derivando de
takana — el hito se demuestra en el laptop, que ya está en ABI 10), `harkaq-policy` derivando de
la clausura real, y el contraste receta-de-Alpinizada vs. receta-que-no.
**Sutileza medida: lo que DAC ya bloquea es invisible.** Un acceso que los permisos Unix rechazan
@@ -775,7 +775,7 @@ DEUDA IRREDUCIBLE — nadie la provee:
/usr/lib/libz.so.1.3.2
```
Las dos son correctas y la segunda es sutil: la receta de zlib de hammer produce `libz.a`
Las dos son correctas y la segunda es sutil: la receta de zlib de takana produce `libz.a`
estático, no un `.so`, así que un consumidor que quiera el shared **no** se arregla declarando.
El diagnóstico deja de ser "algo pasó" y pasa a ser "hacé esto".
@@ -794,7 +794,7 @@ línea. No hay una cola larga de excepciones — que era el riesgo de muerte del
**Y el caso irreducible es la frontera conocida:** brotli (C++) pide
`/usr/lib/libstdc++.so.6.0.34` y `/usr/lib/libgcc_s.so.1` — el runtime C++ del gcc de Alpine, que
hammer no construye. Es exactamente la frontera que la campaña *matar gcc* ya tenía identificada
takana no construye. Es exactamente la frontera que la campaña *matar gcc* ya tenía identificada
("gcc retenido sólo para {kernel, cmake}", "lo que queda: los `*-sys` con C++"). **Tercera vez que
harkaq llega por su cuenta a una lista que otro frente ya tenía**: los swaps del `selfhost-verify`
(§4.3), los binutils (§4.4) y ahora el runtime C++.
@@ -821,14 +821,14 @@ cada paso guiado **sólo** por lo que dijo harkaq:
| final | **`Hermetico` ×3 fases** | — | artefacto `b3:adc5c251…` sellado |
**Cada capa que se pela destapa la siguiente, y todas convergen en el gcc de Alpine.** El último
hallazgo es el más interesante y no lo habría encontrado a mano: **los binutils *de hammer* — ya
hallazgo es el más interesante y no lo habría encontrado a mano: **los binutils *de takana* — ya
declarados como dep — cargan el plugin LTO del gcc *de Alpine*
(`/usr/libexec/gcc/x86_64-alpine-linux-musl/15.2.0/liblto_plugin.so`)**. No es un problema de
declaración de la receta: es un agujero de soberanía *dentro de un artefacto que hammer construye*.
declaración de la receta: es un agujero de soberanía *dentro de un artefacto que takana construye*.
Va a **denegación esperada**, y por la misma razón que `/usr/bin/gcc` (§4.2): el build completa sin
él ⇒ es una *sonda*, no una necesidad, y **bloquearla es deseable** — impide que los binutils de
hammer usen el plugin de Alpine por detrás. D3 otra vez: no se calla, se clasifica.
takana usen el plugin de Alpine por detrás. D3 otra vez: no se calla, se clasifica.
**Detalle que confirma el modelo:** el hash del artefacto es **idéntico** antes y después de
clasificar la sonda (`b3:adc5c251…` las dos veces). Clasificar cambia el *veredicto*, no el
@@ -905,7 +905,7 @@ rollout, no un `sed`.
El primer barrido (§4.6) dejó **un solo** caso irreducible: brotli pidiendo
`libstdc++.so.6.0.34` + `libgcc_s.so.1`. brotli es **C**, no C++ — la libstdc++ no era suya.
Era de `cmake`, su dep de build. El artefacto que **hammer construye**:
Era de `cmake`, su dep de build. El artefacto que **takana construye**:
```
store/99864dcc…-cmake/usr/bin/cmake
@@ -936,7 +936,7 @@ Medido:
sellado `b3:bc900676…`. Su deuda restante (`ld`, `make`) era declarable y se declaró.
El barrido queda **0 irreducibles de 6**. El caso que iba a contar contra el <5% no era una receta
mal escrita: era una herramienta de hammer filtrando Alpine, y harkaq la señaló desde el consumidor.
mal escrita: era una herramienta de takana filtrando Alpine, y harkaq la señaló desde el consumidor.
**Rollout: ✅ HECHO (2026-07-15).** cmake reconstruido en un worker de la granja (regla: la cadena
GUI no se rebuildea en el laptop) y cosechado al hub: `b3:834132e7…-cmake`, `NEEDED` = sólo
@@ -950,7 +950,7 @@ solo en su próximo build. Confundir "usó la herramienta" con "linkea la librer
un rebuild de gtk4 para nada.
**Regalo del rollout:** el worker (Ubuntu, ccx23) y el laptop (CachyOS) produjeron cmake con el
**mismo hash, bit a bit** — el invariante central de hammer confirmándose entre dos máquinas
**mismo hash, bit a bit** — el invariante central de takana confirmándose entre dos máquinas
distintas, sin que nadie lo buscara. Y como los bytes son idénticos, el `NEEDED` del worker está
verificado por transitividad: no hizo falta `readelf` allá (que además no está instalado).
@@ -1016,7 +1016,7 @@ vibra.
de su clausura declarada. Es una afirmación fuerte y defendible. La versión sin asteriscos es la
que te van a cobrar. (Mismo criterio que SDD 15 §0: "verificado bajo supuestos nombrados".)
- **Competencia.** Como proyecto OSS genérico, "sandbox para agentes" es un espacio poblado (ya
existe landrun) y la ventana no es infinita. Como el ejecutor de hammer, no compite con nadie: el
existe landrun) y la ventana no es infinita. Como el ejecutor de takana, no compite con nadie: el
diferenciador no es la jaula, es que **la política sale del hash y la evidencia entra al grafo de
confianza**. Nadie más tiene CAS + log monótono + grafo para cerrar ese bucle.
- **El rebuild del kernel cambia su hash.** Encender `SECURITY_LANDLOCK` invalida
@@ -1042,7 +1042,7 @@ vibra.
mata el vector**; Landlock read-only no alcanzaba, fs-verity sí.
**Costo medido, contra lo que dice el SDD 17 §5:** *«hash = identidad»* es **falso** — fs-verity
usa un Merkle **SHA-256** y hammer direcciona con **BLAKE3**: son **dos** hashes, no uno. Y en
usa un Merkle **SHA-256** y takana direcciona con **BLAKE3**: son **dos** hashes, no uno. Y en
ext4 exige `-O verity` **y blocksize = PAGE_SIZE** (con 1024 el mount falla:
`Unsupported blocksize for fs-verity`). No es gratis, pero es barato para lo que da.
+17 -17
View File
@@ -4,13 +4,13 @@
> "de casualidad" — eran básicas y no estaban en el plan. Este doc responde: ¿qué otros
> cierres, tecnologías de frontera o complementos dan ventaja competitiva, y qué crate o
> arquitectura de `../tawasuyu` los abarata? Es, en la práctica, la "parte 2" de
> `tawasuyu/shared/PLAN-CRUCES.md` (que no menciona a hammer ni una vez).
> `tawasuyu/shared/PLAN-CRUCES.md` (que no menciona a takana ni una vez).
## 0. Tesis
Los tres invariantes ya pagados — **bit-reproducibilidad** (SDD 11/13), **direccionamiento
por contenido** (SDD 06) y **clausura-como-política** (SDD 16) — hacen casi gratis para
hammer tecnologías que para todos los demás son carísimas. La búsqueda correcta no es
takana tecnologías que para todos los demás son carísimas. La búsqueda correcta no es
"qué frontera agregar" sino **"qué cae solo del invariante"**, igual que la jaula cayó del
sandbox y el CRDT cayó de H2.
@@ -27,7 +27,7 @@ Crates de `tawasuyu/shared/` limpios para importar (no_std o casi, deps mínimas
### 1. Consenso de reconstrucción — caché sin confianza
N builders independientes reproducen el mismo hash ⇒ el binario es confiable **sin que
nadie firme nada**. Piezas hammer listas: bit-repro verificada (selfhost, kernel), granja
nadie firme nada**. Piezas takana listas: bit-repro verificada (selfhost, kernel), granja
efímera, embrión del log de transparencia (`bootstrap.json`, SDD 11 §4 / SDD 09 §4).
Nix/Debian no pueden ofrecerlo (repro incompleta). Convierte la repro de QA en **primitiva
de distribución**: cualquiera puede ser mirror, nadie puede envenenar el repo.
@@ -40,7 +40,7 @@ mismo hash que `b3:` — distribución de artefactos por hash en la malla) · `r
(sync de índices de mirrors en O(diferencia)). Economía del reparto: `sheafsync::BudgetRouter`
+ escrow (créditos de cómputo; el gancho a H2 ya está previsto en PLAN-OS-CRDT §E3c).
### 2. `hammer why-differs` — diffoscope propio
### 2. `takana why-differs` — diffoscope propio
Cuando un hash no reproduce, explicar **por qué** (sección, timestamp, orden de símbolos).
Multiplicador doble: hace escalar el barrido de granja (hoy debuggear una no-reproducción
@@ -54,12 +54,12 @@ descenso ES su algoritmo, huellas por rango) · `foreign-fs` (chunking content-d
### 3. Política de runtime derivada del closure
Fase 3 natural de harkaq: la clausura de ficheros medida en build ES la política
Landlock del binario instalado. AppArmor/flatpak escriben políticas a mano; hammer las
Landlock del binario instalado. AppArmor/flatpak escriben políticas a mano; takana las
**deriva**. Nadie tiene esto y la receta sellada ya contiene la evidencia.
**Cruce (oro, el riel existe)**: `ConcesionCapacidad` firmada sobre
`(blake3(binario), permisos)` que arje verifica al arranque + `arje-absorb --attest-from
<json>` que integra concesiones emitidas por hammer (PLAN-ATESTACION §B.3, hecho). La
<json>` que integra concesiones emitidas por takana (PLAN-ATESTACION §B.3, hecho). La
clausura de harkaq son los `permisos` de esa concesión — el veredicto `Hermetico` se
convierte en política atestada sin inventar formato. Filosofía de referencia: el modelo
wawa "capacidad = frontera física, no tabla de permisos" (WAWA.md §4.2). Lado runtime:
@@ -140,9 +140,9 @@ se ensancha en silencio.
### 4. CVE por grafo
`hammer affected CVE-X` exacto (grafo fuente→artefacto→instalado) + frontera mínima de
`takana affected CVE-X` exacto (grafo fuente→artefacto→instalado) + frontera mínima de
rebuild + hydrate para repartir el parche rebuild-free. Donde Debian/Alpine aproximan por
nombre-versión, hammer responde exacto. Tabla de mesa para adopción seria.
nombre-versión, takana responde exacto. Tabla de mesa para adopción seria.
**Cruce**: `dataflow` (`DepGraph<K>` no_std: `dirty_cone` = literalmente la frontera
mínima de rebuild; `topo_order` = la agenda determinista) · `grafo-nav` (cono/verbos).
@@ -161,7 +161,7 @@ la capa.
### 6. Matar el trusting-trust: DDC (Diverse Double-Compiling)
Con la cadena mrustc cerrada y bit-repro verificada, hammer es de los poquísimos
Con la cadena mrustc cerrada y bit-repro verificada, takana es de los poquísimos
proyectos del mundo que puede **ejecutar** DDC: compilar la cadena partiendo de Alpine vs
partiendo de sí misma y comparar hashes. Un fin de semana de granja, resultado
publicable. Largo plazo: semilla full-source (stage0/Mes).
@@ -171,17 +171,17 @@ publicable. Largo plazo: semilla full-source (stage0/Mes).
### 7. De-Alpinizar el rootfs del sandbox
El hallazgo del plugin LTO (binutils de hammer cargando el gcc de Alpine) mostró un canal
El hallazgo del plugin LTO (binutils de takana cargando el gcc de Alpine) mostró un canal
impuro **estructural** mientras el sandbox se apoye en Alpine. Harkaq lo mide; el cierre
es rootfs de build 100% hammer-built (el patrón de de-Alpinización ya existe).
es rootfs de build 100% takana-built (el patrón de de-Alpinización ya existe).
**Cruce**: ninguno directo (trabajo interno de hammer). Disciplina análoga en tawasuyu:
**Cruce**: ninguno directo (trabajo interno de takana). Disciplina análoga en tawasuyu:
`scripts/check-shared-cores.sh` (guardián de simetría no_std).
### 8. `hammer oci`
### 8. `takana oci`
Emitir imágenes distroless **bit-reproducibles** desde un closure (closure → capas →
manifest). Vector de adopción realista: la gente consume hammer vía contenedores años
manifest). Vector de adopción realista: la gente consume takana vía contenedores años
antes de instalar la distro.
**Cruce**: `format`/`foreign-fs` para capas dedup por contenido · `card-net` como
@@ -208,7 +208,7 @@ del simulador" — mapea 1:1 a receta+veredicto-harkaq) · `willay-hilo` (caja n
por-device sobre fork-proof ⇒ bucle agéntico **no repudiable**) · `atipay` (catálogo
determinista de capacidades → plan tool-use) · `sandokan-journal` (replay del plano de
control) · `rag-motor` (contrato RAG con citación, alimentado por why-differs).
PLAN-ATESTACION ya enuncia el ciclo: "IA propone → humano commitea (hammer) → init
PLAN-ATESTACION ya enuncia el ciclo: "IA propone → humano commitea (takana) → init
atesta (arje)".
## 2. Qué NO priorizar
@@ -220,11 +220,11 @@ atesta (arje)".
## 3. Nota CRDT
El cierre CRDT en hammer = journal/catálogo replicado granja↔laptop sin git como bus
El cierre CRDT en takana = journal/catálogo replicado granja↔laptop sin git como bus
(anti-entropy `crdt::wire` sobre el LwwMap de H2). El transporte real vive en
PLAN-OS-CRDT §E3 (tawasuyu) — no duplicar. Estado real de ese plan a 2026-07-15: **E2
(`fork-proof`) y E3c (`BudgetRouter`) ya son código con tests; E1 (MonotonicLog en el
kernel wawa) sigue siendo plan** — la parte que hammer necesita ya existe.
kernel wawa) sigue siendo plan** — la parte que takana necesita ya existe.
## 4. Caveats del cruce
+8 -8
View File
@@ -1,16 +1,16 @@
# SDD 18 — wawafs: el store como filesystem del sistema vivo (CAS + erofs + fs-verity)
> Origen: la pregunta "¿se podría hostear hammer en un FS de grafos sobre discos en vivo,
> Origen: la pregunta "¿se podría hostear takana en un FS de grafos sobre discos en vivo,
> sacándole ventaja a ext4?" (2026-07-17). La respuesta corta: sí, y no hay que escribir un
> filesystem — la pila composefs (CAS + erofs + overlayfs + fs-verity, mainline) es exactamente
> esa idea, y el modelo de hammer ya es casi isomorfo a ella. "wawafs" queda como apodo del
> esa idea, y el modelo de takana ya es casi isomorfo a ella. "wawafs" queda como apodo del
> frente; no es código de wawa (tawasuyu), es la misma filosofía content-addressed bajada al VFS.
**Estado:** diseño aceptado en conversación; sin implementar. **Fecha:** 2026-07-17.
## 1. Problema
Hoy la materialización del store al sistema vivo es **por copia u hardlink** (`hammer hydrate
Hoy la materialización del store al sistema vivo es **por copia u hardlink** (`takana hydrate
<hash> --into`, ADR 0005). Funciona y escala rebuild-free (el escritorio KDE son 137 artefactos
hidratados), pero en el **sistema desplegado** (live-disk, metal, generaciones de boot) paga
tres costos:
@@ -27,7 +27,7 @@ tres costos:
**Bajar el checkout de userspace al kernel**: hoy `hydrate` es un checkout con copia (git-style:
store = DAG, hidratar = materializar el árbol); wawafs es el mismo checkout hecho *lazy* por el
VFS al montar, sin copiar un byte. ext4 queda abajo como capa de bloques tonta; la "evolución
sobre ext4" es la capa content-addressed encima — la arquitectura que hammer ya tiene en
sobre ext4" es la capa content-addressed encima — la arquitectura que takana ya tiene en
userspace, un nivel más abajo.
## 3. Las capas
@@ -61,7 +61,7 @@ Propiedades que caen solas:
No se toca.
- **Grano archivo** (nuevo, derivado): el CAS de objetos se indexa por el **digest fs-verity**
(sha256, es lo que el kernel sabe verificar). Es una capa *derivada* del store: un comando
(`hammer fs manifest <ArtifactHash…>`) explota los árboles de N artefactos en objetos por
(`takana fs manifest <ArtifactHash…>`) explota los árboles de N artefactos en objetos por
archivo + genera el manifest erofs de la clausura. blake3 sigue siendo la identidad; el
digest verity es el enforcement.
@@ -103,19 +103,19 @@ un mapa entre ellos).
(mismo patrón que el re-sellado LANDLOCK/IO_URING de frente-kikin).
2. **Recetas nuevas**: `erofs-utils` (mkfs.erofs) y `composefs` (mkcomposefs) — C, no existen
en el catálogo → **worker** (el laptop no construye recetas C).
3. **Conversor**: `hammer fs manifest` — explotar artefactos en objetos por archivo. El store
3. **Conversor**: `takana fs manifest` — explotar artefactos en objetos por archivo. El store
actual (1608 artefactos, directorios por hash) no se toca; el CAS de objetos es derivado y
regenerable.
## 8. Fases
- **F0 — spike en QEMU** (sin tocar nada de hammer): rootfs Alpine con composefs-tools de
- **F0 — spike en QEMU** (sin tocar nada de takana): rootfs Alpine con composefs-tools de
paquete, `mkcomposefs` sobre UNA clausura hidratada (p.ej. la de KDE), montar, comparar
`find | sort | b3sum` contra la hidratación por copia. Gate: árboles idénticos + medir
open-frío y RAM compartida. Decide si el frente existe.
- **F1 — kernel + recetas**: configs EROFS/FS_VERITY en `linux-generic.toml` (re-sellar en
worker) + recetas `erofs-utils`/`composefs`. Gate: el kernel propio montando el spike de F0.
- **F2 — `hammer fs manifest` + generaciones**: manifest erofs por clausura, generación de boot
- **F2 — `takana fs manifest` + generaciones**: manifest erofs por clausura, generación de boot
= manifest por hash, arje monta `/usr` de la generación elegida (contrato ADR 0010). Gate:
bootear en QEMU una generación montada, rollback = elegir otro nodo en el menú.
- **F3 — sellado verity + live media**: fs-verity en el CAS de objetos; evaluar erofs como
+2 -2
View File
@@ -68,7 +68,7 @@ Esto es lo que más fácil se pasa por alto y lo que puede obligar a bajar una d
> [SDD 21](21-manual-del-piloto.md).
Hace falta:
- Un campo de licencia **obligatorio** en la receta (con validación en `hammer`, no por convención).
- Un campo de licencia **obligatorio** en la receta (con validación en `takana`, no por convención).
- El texto de la licencia **dentro del artefacto** (`/usr/share/licenses/<pkg>/`), que es lo que
exigen GPL y familia al distribuir binarios.
- Un informe agregado por imagen: qué licencias entran en cada perfil y si alguna es incompatible.
@@ -86,7 +86,7 @@ después.
Redistribuir Firefox con su nombre y logo requiere cumplir la política de marca de Mozilla; por eso
Debian tuvo Iceweasel. Lo mismo con otros. Decidir por adelantado: cumplir o rebrandear.
### 2.4 La licencia de hammer mismo — ✅ ya está
### 2.4 La licencia de takana mismo — ✅ ya está
> **CORRECCIÓN (mismo día).** Este punto decía «el repo no declara una». **Es falso**: hay un
> `LICENSE` (MIT) en la raíz y `license = "MIT"` en `Cargo.toml`. No lo miré antes de escribirlo.
> Este punto no bloquea nada.
+8 -8
View File
@@ -56,7 +56,7 @@ número se repite: es el mismo agujero visto desde cuatro grafos):
```
gtk4 libadwaita gtksourceview gtk4-hello adwaita-hello sourceview-hello
hammer-edit mirada-compositor mirada-greeter dwarves llimphi-counter wlr-randr
takana-edit mirada-compositor mirada-greeter dwarves llimphi-counter wlr-randr
```
> ### ⚠️ CORREGIDO EL MISMO DÍA, MIDIENDO CONTRA EL WORKER
@@ -73,7 +73,7 @@ symbol`. No es una receta rota, es una imposibilidad estructural. Mientras tanto
`recipes/incoming-gnome/gtk4.toml``link = "dynamic"` con las deps `-shared`**está sellada**, y
con ella `libadwaita` y `fontconfig-shared`.
⇒ La cadena del corpus (`gtk4`, `libadwaita`, `gtksourceview`, `gtk4-hello`, `adwaita-hello`,
`sourceview-hello`, `hammer-edit`) es un **duplicado superado** de la cadena dinámica de GNOME.
`sourceview-hello`, `takana-edit`) es un **duplicado superado** de la cadena dinámica de GNOME.
Cerrarla no es «construirla»: es decidir si se **promueven las sombras `-shared` al corpus**, porque
la resolución de deps es hermano→padre y una receta del corpus no ve a las de `incoming-gnome`.
**Es una decisión de arquitectura, no una tarea** — y hay un aviso registrado de que promover a
@@ -130,11 +130,11 @@ vacía — convierte un hueco visible en una afirmación falsa. Concretamente se
y toda la familia `kube*` son herramientas Go sin relación con KDE.
### El texto de la licencia dentro del paquete — ✅ HECHO (y no donde yo dije)
> **CORRECCIÓN.** Esto decía «va en `hammer pack`». El razonamiento del coste era correcto —las
> **CORRECCIÓN.** Esto decía «va en `takana pack`». El razonamiento del coste era correcto —las
> fases SÍ entran al hash, así que hacerlo en la receta re-hashearía las 1141— pero **el sitio
> estaba mal**: `pack` produce un `.swm`, una receta de transformación sobre fuente pública que por
> diseño «NUNCA transporta binarios cocidos», e `install` REPRODUCE construyendo. **El canal de
> paquetes de hammer no distribuye binarios.** Donde sí se entrega un binario es en la **imagen
> paquetes de takana no distribuye binarios.** Donde sí se entrega un binario es en la **imagen
> instalable**, y ahí se hace ahora: `scripts/licencias-rootfs.sh` escribe
> `/usr/share/licenses/<pkg>/` con el texto canónico de cada licencia de la expresión, más un
> `MANIFEST.tsv` (1,0 MB para el perfil `cli`). Los 44 textos SPDX viven en `licenses/`.
@@ -206,11 +206,11 @@ del catálogo publicable y del reporte de licencias: su receta lleva `foreign =
clasifica `ajeno` y su `license` es un `LicenseRef-…-no-enumerable` explícito, que dice lo que hay
—cientos de paquetes Debian que no podemos enumerar— en vez de dejar el campo vacío, que se leería
como «todavía no la poblamos». Y la distinción del punto anterior sigue en pie con más filo:
**replicarla a nuestras propias máquinas** con `hammer mirror push` es lo mismo que ya hace el ADR
**replicarla a nuestras propias máquinas** con `takana mirror push` es lo mismo que ya hace el ADR
0013 con las fuentes; **publicarla a terceros** sigue pidiendo mirar licencia y marca antes, y eso
no es una pregunta técnica.
⚠️ Y el corolario que hay que escribir donde se lea: **el claim «hammer reproduce bit a bit» hay que
⚠️ Y el corolario que hay que escribir donde se lea: **el claim «takana reproduce bit a bit» hay que
acotarlo** desde el día que exista una instancia — *el sistema base reproduce; las instancias ajenas
no, y se declaran como tales*. Sin eso, la cultura de números honestos se erosiona sola.
@@ -219,7 +219,7 @@ Redistribuir Firefox con su nombre y logo exige cumplir la política de marca de
Debian tuvo Iceweasel). Decidir por adelantado: cumplir o rebrandear. **No bloquea el primer
lanzamiento** si se sale sin navegador propio.
### La licencia de hammer — ✅ ya está
### La licencia de takana — ✅ ya está
`LICENSE` en la raíz y `license = "MIT"` en `Cargo.toml`. *(El SDD 19 decía que faltaba; era falso.)*
## I.3 Infraestructura pública
@@ -344,7 +344,7 @@ originó el respaldo del 2026-08-07.
**Para PUBLICABLE falta, en una línea cada uno:**
1. Arreglar el entorno del worker → caen las 12 recetas en deuda.
2. Terminar las licencias (913) e inyectar el texto en `hammer pack`.
2. Terminar las licencias (913) e inyectar el texto en `takana pack`.
3. Montar el espejo de fuentes.
4. Clave raíz fuera de línea y procedimiento de rotación.
5. Imagen validada en metal (re-quemar el USB).
+2 -2
View File
@@ -112,7 +112,7 @@ ojo. Un hueco contado es deuda; un hueco rellenado a ojo es una mentira sobre la
construir.
### Paso 3 — El texto de la licencia dentro del paquete
**[agente]** Va en `hammer pack`, **no** en la fase `install` de las recetas. Las fases entran al
**[agente]** Va en `takana pack`, **no** en la fase `install` de las recetas. Las fases entran al
`ArtifactHash`, así que hacerlo ahí re-hashearía las 1141; pack es aguas abajo y sale gratis.
### Paso 4 — El espejo de fuentes
@@ -237,7 +237,7 @@ que sostener:
originó el respaldo del 2026-08-07. Si un día deja de funcionar, te enterás por el ensayo y no por
una desgracia.
4. **El reconstructor independiente**: otra máquina, idealmente de otra persona, que reconstruya y
compare hashes en continuo. Es el objetivo final del invariante de hammer — el día que un tercero
compare hashes en continuo. Es el objetivo final del invariante de takana — el día que un tercero
confirme bit a bit lo que publicamos, la promesa deja de ser nuestra palabra.
5. **Contribuciones de fuera**: si llega gente, hace falta cómo aceptar recetas sin bajar el listón
(revisión, firma, y que el aporte no rompa la reproducibilidad).
+14 -14
View File
@@ -3,7 +3,7 @@
Escrito 2026-08-07, en respuesta a `tawasuyu/HANDOFF-KERNEL-CONFIG-A-HAMMER.md`.
El handoff avisa de su propio método: *«nada de lo que sigue está verificado contra el código de
hammer — si un dato de hammer lo contradice, manda hammer»*. Este documento es esa verificación. **No
takana — si un dato de takana lo contradice, manda takana»*. Este documento es esa verificación. **No
es una aprobación ni un plan de implementación**: contesta las seis preguntas de su §9 con evidencia,
corrige lo que estaba mal, y señala dos objeciones técnicas que cambian el diseño.
@@ -16,7 +16,7 @@ objeción técnica a §2.1** que hay que resolver antes de escribir una línea.
## 1. El hecho que reordena todo: **el config ES la identidad del artefacto**
En hammer, `Recipe::hash_inputs` es una lista blanca, y **las fases de build entran en ella**. El
En takana, `Recipe::hash_inputs` es una lista blanca, y **las fases de build entran en ella**. El
config del kernel vive dentro de la fase `configure` de `recipes/linux*.toml`. Por lo tanto:
> **Cambiar un solo símbolo de config cambia el `ArtifactHash` del kernel.**
@@ -26,7 +26,7 @@ Esto no es un detalle de implementación: es la bisagra del diseño, y corta en
**A favor, y fuerte:** §2.2 (config atestado por huella de hardware) **sale casi gratis**. No hay que
inventar un mecanismo para «atestar un config»: cada config ya *es* un artefacto direccionado por
contenido, reproducible bit a bit y firmable con la maquinaria que existe. Un «config que booteó en
esta huella» es, literalmente, un `ArtifactHash` + una firma. El handoff intuyó que hammer tenía el
esta huella» es, literalmente, un `ArtifactHash` + una firma. El handoff intuyó que takana tenía el
sustrato; lo tiene más de lo que creía.
**En contra, y también fuerte:** la explosión de builds que §2.2 quiere evitar **no es hipotética,
@@ -38,7 +38,7 @@ condición de supervivencia del disco**, y conviene decirlo así en la UI.
**Consecuencia práctica que hay que aceptar desde el día uno:** una «perilla» de la UI **no es un
parámetro de runtime, es una edición de receta**. No hay forma de tener un config variable sin
generar recetas. El diseño correcto es que `hammer kernel plan` **emita una receta derivada** (con su
generar recetas. El diseño correcto es que `takana kernel plan` **emita una receta derivada** (con su
propio hash) en vez de fingir que el kernel es un binario parametrizable.
---
@@ -202,10 +202,10 @@ cae cada perilla o prometerá diffs que no puede explicar.
De acuerdo con el reparto tal como está. Dos precisiones desde este lado:
- El contrato JSON: de acuerdo, y el precedente que cita (`/run/hammer/boot-graph.json`) es el
correcto. Añadiría que el JSON de `hammer kernel plan` **debe incluir el `ArtifactHash` resultante**,
correcto. Añadiría que el JSON de `takana kernel plan` **debe incluir el `ArtifactHash` resultante**,
porque por §1 el plan *determina* el artefacto: la UI puede decir «esto ya está construido y
firmado» sin construir nada.
- Los verbos propuestos me parecen bien salvo `hammer kernel build --plan`, que esconde dos cosas muy
- Los verbos propuestos me parecen bien salvo `takana kernel build --plan`, que esconde dos cosas muy
distintas (construir, y dar de alta en el árbol de generaciones). Separarlos deja que la sonda en
VM de §4 se meta entre medio como un paso propio y auditable.
@@ -231,11 +231,11 @@ el orden del §8.
## 9. Paso 2 hecho: la clausura SÍ reproduce los bundles a mano
`hammer kernel` (`crates/hammer-core/src/kernel/`, `crates/hammer-cli/src/kernel_cmd.rs`) trae un
`takana kernel` (`crates/hammer-core/src/kernel/`, `crates/hammer-cli/src/kernel_cmd.rs`) trae un
lector de Kconfig **de sólo análisis** — la regla dura del §6 del handoff se respeta literalmente: no
hay solver, el `.config` lo sigue produciendo `olddefconfig`.
Salud del lector, sobre `linux-6.16.12` (`hammer kernel stats`):
Salud del lector, sobre `linux-6.16.12` (`takana kernel stats`):
| ficheros | símbolos | visibles | con ayuda | aristas `select` | aristas `imply` | avisos de parseo |
|---|---|---|---|---|---|---|
@@ -262,7 +262,7 @@ un kernel que arranca. Pregunta: *¿la clausura calculada desde la raíz mínima
escribió el humano?*
```
hammer kernel closure --kconfig <árbol> WIRELESS --fixpoint
takana kernel closure --kconfig <árbol> WIRELESS --fixpoint
```
| | símbolos |
@@ -306,13 +306,13 @@ una decisión, y por eso el punto fijo la muestra en vez de aplicarla.
## 10. Pasos 1 y 4 hechos: modo reversa, plan y diff-back
**Modo reversa** (`hammer kernel probe`) sobre gioser, con el catálogo `docs/state/kernel-bundles.toml`
**Modo reversa** (`takana kernel probe`) sobre gioser, con el catálogo `docs/state/kernel-bundles.toml`
(15 bundles N1, 8 perillas N2): 10 551 símbolos declarados, 20 dispositivos PCI, 38 drivers
bindeados, **7 bundles con capacidad que este hardware no usa**. Y avisa de lo que el propio caso
destapó: el config vivo es de la serie 7.1 y el catálogo se revisó contra la 6.16 ⇒ las clausuras
son aproximadas. Se dice en vez de callarlo.
**Plan** (`hammer kernel plan`): emite una **receta derivada**, como manda §1. Medido sobre
**Plan** (`takana kernel plan`): emite una **receta derivada**, como manda §1. Medido sobre
`recipes/linux.toml` con `sin-wifi` + `sin-audio` + `solo-ext4` + `jaula-y-eio-moderna`:
| | |
@@ -327,7 +327,7 @@ plan lleva su hash dentro ⇒ la UI puede decir «esto ya está construido y fir
Tres detalles del plan que no eran obvios:
- **Se emiten raíces, no clausuras.** 16 banderas, no 1775 líneas. La clausura la calcula el
`olddefconfig` del propio kernel; hammer la sabe sólo para poder explicarla.
`olddefconfig` del propio kernel; takana la sabe sólo para poder explicarla.
- **La fase derivada AÑADE una segunda ronda** (`… && scripts/config … && make olddefconfig`) en vez
de reescribir la base. No hay que parsear el shell de nadie y la base sigue siendo literalmente la
de siempre en el diff.
@@ -336,7 +336,7 @@ Tres detalles del plan que no eran obvios:
que el símbolo solo no delata: encender algo que **cae dentro de la clausura** de lo que otro
bundle apaga — `olddefconfig` lo descartaría sin decir nada.
**Diff-back** (`hammer kernel diff-back --plan … --config …`): la mitad que faltaba del §6 del
**Diff-back** (`takana kernel diff-back --plan … --config …`): la mitad que faltaba del §6 del
handoff. Clasifica cada símbolo pedido en cumplido / **incumplido** (el `.config` dice otra cosa) /
ausente (el kernel ni lo menciona: la bandera fue un no-op), con la procedencia de quién lo pidió, y
sale distinto de cero si el config no honra el plan.
@@ -446,7 +446,7 @@ kernel que corre y **`linux-metal` no los enciende nunca**. No era una regresió
hueco de la receta base, invisible para el gate del §11 por construcción.
Lo mismo con `VIRTIO_BALLOON` y `HW_RANDOM_VIRTIO`: **ninguna receta de kernel del repo los
enciende** y en gioser los dos están bindeados. Un kernel de hammer arranca igual en esta máquina,
enciende** y en gioser los dos están bindeados. Un kernel de takana arranca igual en esta máquina,
pero la VM se queda sin globo de memoria y sin la entropía del anfitrión.
⇒ Tres entradas nuevas de catálogo, todas nacidas de medir: bundle `sin-gpu-intel` (i915 es de los
+3 -3
View File
@@ -40,7 +40,7 @@ clase de error que la regla del `.a` no-PIC —`grep R_X86_64_32` contaba reubic
`.debug_*`— y que el conteo de licencias con `grep -l license`, que contaba comentarios.
**(c) La prueba que decide: reconstruir y comparar.** Se apartó el artefacto de `wl-clipboard`, se
reconstruyó con las deps cacheadas y se comparó con `hammer why-differs`:
reconstruyó con las deps cacheadas y se comparó con `takana why-differs`:
```
24 entradas idénticas · 0 divergen
@@ -129,7 +129,7 @@ Al buscar dónde poner una flag global apareció algo que hay que decidir antes
`crates/hammer-build/src/sandbox.rs` fija el entorno de TODOS los builds:
```
CC=hammer-zig-cc (con -mcpu=baseline) SOURCE_DATE_EPOCH=1 TZ=UTC LC_ALL=C
CC=takana-zig-cc (con -mcpu=baseline) SOURCE_DATE_EPOCH=1 TZ=UTC LC_ALL=C
AR="zig ar" CARGO_BUILD_… codegen-units=1
```
@@ -325,7 +325,7 @@ worker: cargo AUSENTE · rustc AUSENTE · go AUSENTE
hub: cargo ✓ · rustc ✓ · go ✓ (~/.cargo/bin)
```
`hammer` invoca **`cargo vendor` en el HOST**, no dentro del sandbox hermético — el vendoreo pasa
`takana` invoca **`cargo vendor` en el HOST**, no dentro del sandbox hermético — el vendoreo pasa
antes de entrar a la caja. El worker no trae ningún toolchain de Rust ni Go, así que **la granja no
puede construir NINGUNA receta Rust o Go**:
+5 -5
View File
@@ -107,7 +107,7 @@ BUILD (sandbox, N en paralelo, cero locks)
- **R7** — Fallo o kill durante materialización ⇒ sólo queda `tmp/`; la corrida siguiente lo barre
y re-materializa. Nunca se sella parcial.
- **R8** — La adopción **no mueve ningún `artifact_hash`**: `fuente_id` no entra en `hash_inputs`.
Verificación pre-merge: `hammer hash --check` sobre el corpus, cero movidos.
Verificación pre-merge: `takana hash --check` sobre el corpus, cero movidos.
- **R9** — GC de `sources-cas` por alcanzabilidad desde recetas activas; lo no alcanzable es
podable (regenerable desde el mirror ADR 0013). Medir por contenido, nunca con `du`.
- **R10** — Re-materializar un `fuente_id` existente debe reproducir el `blake3_arbol` del marker
@@ -123,7 +123,7 @@ BUILD (sandbox, N en paralelo, cero locks)
- **IF-2** — Canario deliberado (estilo D9 de harkaq): un build de test **escribe** en `/src`
(debe FUNCIONAR, vía copy-up al upper) y el árbol sellado **sigue idéntico** por `blake3_arbol`.
Si el hash se movió, alguien montó el lower como bind RW. No se cree: se gana.
- **IF-3**`hammer hash --check` corpus completo: hash vigente idéntico pre/post migración.
- **IF-3**`takana hash --check` corpus completo: hash vigente idéntico pre/post migración.
- **IF-4**`kill -9` en medio de la materialización ⇒ no existe marker; la corrida siguiente
sella limpio y `blake3_arbol` casa con una materialización sin interrupción.
- **IF-5** — Artefacto sobre overlay ≡ bit-a-bit al del modelo viejo, **una receta por clase y
@@ -175,7 +175,7 @@ BUILD (sandbox, N en paralelo, cero locks)
log**, nunca silenciosa.
- **D3**`--keep-ws` acumula workspaces; podarlos en el GC de R9.
- **D4** — El snapshot golden debe hornear `sources-cas` o los primeros builds pagan
materialización fría. **Choca con el test de `hammer-bootstrap` (`lib.rs:2659`) que afirma que
materialización fría. **Choca con el test de `takana-bootstrap` (`lib.rs:2659`) que afirma que
`work/sources/` no viaja en el builder rootfs**: la decisión (¿el builder hereda `sources-cas`?)
se toma en F2.5 con el dato del slice, y si es sí, ese test se actualiza a propósito — no se
deja fallar por sorpresa.
@@ -205,7 +205,7 @@ regla 1 (retiro del flock en R11) · `build-farm.sh` Fase 1b (carrera B; se borr
> Las correcciones que salieron de aquí **ya están incorporadas en el cuerpo de la v2**. Esta
> sección queda como registro reproducible: qué se midió, con qué comando y con qué salida, para
> que nadie tenga que volver a descubrirlo. Método del SDD 22: un diseño entrante se contesta con
> evidencia, y si un dato de hammer lo contradice, manda hammer.
> evidencia, y si un dato de takana lo contradice, manda takana.
## 8.1 El `chmod a-w` recursivo rompe el build (origen de R1)
@@ -263,7 +263,7 @@ byte-intacto y el residuo aislado en el upper.
| Carrera B y su reintento serial (Fase 1b) | `build-farm.sh:82-99` | ✅ |
| Watchdog acoplado al esquema de rutas | `farm-worker-loop.sh:43` | ✅ |
| Purga del árbol viejo | `reconstruir-corpus.sh:206` | ✅ |
| `work/sources/` no viaja en el builder rootfs (test) | `hammer-bootstrap/src/lib.rs:2659` | ✅ (origen de D4) |
| `work/sources/` no viaja en el builder rootfs (test) | `takana-bootstrap/src/lib.rs:2659` | ✅ (origen de D4) |
| `.zwrap` escrito en el árbol de fuentes | 131 recetas *(medido)* | ✅ |
| `--overlay RWSRC WORKDIR DEST` disponible | bubblewrap 0.11.2 | ✅ |
| La tabla de fases dice "patch en sandbox" | `docs/02-build-lab.md` §4 | ⚠️ desactualizada — F1 |
+34 -34
View File
@@ -15,21 +15,21 @@ El espejo de este documento para el lado tawasuyu es
## 0. La respuesta corta
1. **Un módulo cargable no es una opción en hammer y no hace falta.** Las tres recetas de
1. **Un módulo cargable no es una opción en takana y no hace falta.** Las tres recetas de
kernel (`linux-generic.toml:43`, `linux-metal.toml:104`, `linux-metal-dual.toml:51`)
configuran `-d MODULES`: kernel monolítico. `linux.toml` no lo apaga pero sólo compila
`bzImage`, nunca `make modules`. Cualquier código de kernel propio en hammer es
`bzImage`, nunca `make modules`. Cualquier código de kernel propio en takana es
**built-in**, o sea un parche a la receta — que además es lo correcto para el proyecto,
porque la fase `configure` **es** la identidad del artefacto (SDD 22) y así queda sellado
y atestado junto al kernel.
2. **Casi todo lo que duele ya tiene salida en mainline y nadie la está usando.** `pidfd`,
`cgroup` v2 agregado, `posix_spawn`, `getdents64`, `close_range`, `openat2`. Cero código
de kernel, ganancias de 3× a 17× medidas abajo.
3. **La medición encontró un bug cruzado que no se buscaba**: los kernels de hammer se
3. **La medición encontró un bug cruzado que no se buscaba**: los kernels de takana se
construían **sin `CONFIG_MEMCG`**, y arje/sandokan escriben `memory.max` ignorando el
error. Un límite de memoria pedido por una Card **no se aplicaba y nadie se enteraba**. §4.
Confirmado sobre los **11 `.config` sellados**, no deducido del defconfig (§4.bis), vigilado
por `hammer kernel contract` (§4.ter) y **PAGADO el 2026-08-30**: los cuatro kernels que
por `takana kernel contract` (§4.ter) y **PAGADO el 2026-08-30**: los cuatro kernels que
hospedan Cards se reconstruyeron con `MEMCG` y `PSI` y el barrido sale **5 de 5 vigentes en
verde** (§7-H1).
4. **Sondear `/proc` no es lento, es ciego** (§9.1): de 200 procesos de vida mínima, un sondeo
@@ -107,7 +107,7 @@ salida está dentro del propio POSIX.
| glibc DINÁMICO | 324,3 |
`ld.so` cuesta **+92 µs** (+39%). Pero el salto grande es glibc→musl: **4,2× entre el peor y
el mejor**, con el mismo `main(){return 0;}`. El corpus de hammer ya está del lado bueno
el mejor**, con el mismo `main(){return 0;}`. El corpus de takana ya está del lado bueno
(verificado: los binarios del store son `statically linked`); esto **cuantifica** lo que la
de-Alpinización compró: un build que lanza 20 000 procesos se ahorra ~5 s sólo en arranque.
@@ -245,7 +245,7 @@ las dos corridas independientes (128 MiB y 64 MiB).
`fork+exec` base 1 649 µs · con `unshare` de 6 namespaces 2 742 µs · con `USER|MNT` 2 050 µs.
**+1,09 ms** por el juego completo, **+0,40 ms** por USER|MNT.
Para hammer es ruido por fase de build (segundos a minutos). Sería caro **sólo si se
Para takana es ruido por fase de build (segundos a minutos). Sería caro **sólo si se
enjaulara por-exec**: 20 000 execs × 1,09 ms = 22 s.
### T15 · Cerrar descriptores antes del `exec`
@@ -524,18 +524,18 @@ Las anoto aparte porque contradicen el consejo de manual:
deps sólo se pagaría sola a partir de 35 200 primeros fallos en una misma fase. Lo que sí
justifica fundirlas es el muro de una página del `mount(2)`, que está en 42 capas.
## 4. El hallazgo cruzado: los kernels de hammer no tienen `MEMCG`
## 4. El hallazgo cruzado: los kernels de takana no tienen `MEMCG`
No lo buscaba; salió de verificar la recomendación antes de escribirla.
**Lado hammer.** Las recetas hacen `make ARCH=x86_64 defconfig` y después
**Lado takana.** Las recetas hacen `make ARCH=x86_64 defconfig` y después
`scripts/config`. Lo que `arch/x86/configs/x86_64_defconfig` trae de contabilidad:
`CONFIG_TASKSTATS=y`, `TASK_DELAY_ACCT=y`, `CGROUPS=y`, `CGROUP_SCHED=y`, `CGROUP_PIDS=y`,
`CGROUP_FREEZER=y`, `CONNECTOR=y` ⇒ y `PROC_EVENTS` es `default y` con `depends on
CONNECTOR=y`, así que **también entra**.
**`CONFIG_MEMCG` no está en el defconfig y no tiene `default y` ⇒ queda en `n` en todos los
kernels de hammer.** `CONFIG_PSI`, igual.
kernels de takana.** `CONFIG_PSI`, igual.
**Lado tawasuyu.** `sandokan-core::Engine` expone `set_memory_max` / `set_memory_high`
(`shared/sandokan/sandokan-core/src/engine.rs:60-72`), `sandokan-local` los delega a
@@ -549,7 +549,7 @@ match std::fs::write(&path, format!("{mem}\n")) {
Err(e) => tracing::warn!(..., "memory.max write failed"), // sólo un warn
```
**Consecuencia.** Sobre un sistema hammer, una Card que pide un tope de memoria arranca
**Consecuencia.** Sobre un sistema takana, una Card que pide un tope de memoria arranca
**sin tope**, y la única huella es una línea de log. Es exactamente la forma de fallo que
`CLAUDE.md` §3 nombra: *un ausente falla ruidosamente; un vacío llega hasta el final diciendo
que todo fue bien*. La jaula que no está se comporta igual que la jaula que sí está, hasta
@@ -578,7 +578,7 @@ arje ya usa), `TASKSTATS`+`CONNECTOR`+`PROC_EVENTS`, `SECCOMP_FILTER`, `SECURITY
trae `MEMCG`, `PSI` y `CFS_BANDWIDTH`. El fallo sólo existe del lado del artefacto sellado, que es
el lado que nadie mira con un depurador.
### 4.ter · El guardián: `hammer kernel contract`
### 4.ter · El guardián: `takana kernel contract`
Un hallazgo escrito en un documento no impide que vuelva a pasar. El contrato vive en
`docs/state/kernel-contract.toml` y declara, por capacidad: los símbolos que la construyen, la
@@ -587,9 +587,9 @@ consumidor con ruta y símbolo**, y **cómo falla hoy si no está**. Se comprueb
ya producido:
```sh
hammer --store ./store kernel contract --sealed # barre los kernels sellados
hammer kernel contract --profile anfitrion-cards # el kernel vivo de esta máquina
hammer kernel contract --list # qué promete hammer (esto es el W6)
takana --store ./store kernel contract --sealed # barre los kernels sellados
takana kernel contract --profile anfitrion-cards # el kernel vivo de esta máquina
takana kernel contract --list # qué promete takana (esto es el W6)
```
Tres decisiones que valen la pena nombrar:
@@ -600,7 +600,7 @@ Tres decisiones que valen la pena nombrar:
unidad son `wants` y no `requires`.
2. **Contra el `.config`, no contra la receta** — entre el `scripts/config -e X` y el `.config`
está `olddefconfig`, que puede tragarse el símbolo sin decir nada (es justo lo que
`hammer kernel diff-back` ya vigila para los planes).
`takana kernel diff-back` ya vigila para los planes).
3. **Distingue apagado de ausente** — un símbolo que el `.config` ni nombra puede ser un
renombrado entre versiones. Cuenta igual como capacidad ausente, pero se informa aparte; y un
kernel sin perfil declarado queda **SIN COMPROBAR** en vez de contar como aprobado.
@@ -608,7 +608,7 @@ Tres decisiones que valen la pena nombrar:
sellados, no el último. Mirándolos a todos por igual, arreglar la receta dejaba el gate rojo
para siempre por artefactos que nadie va a volver a construir, y **un portón que no puede
ponerse verde deja de leerse**. Ahora cada sellado se compara con el `ArtifactHash` que la
receta de hoy produce —la misma vigencia de `hammer hash --check`, lab incluido—: el vigente
receta de hoy produce —la misma vigencia de `takana hash --check`, lab incluido—: el vigente
bloquea; el superado sale en su propia sección con lo que le falta, porque sigue siendo cierto
que una máquina que arranque ese kernel corre sus Cards con lo que ese kernel traiga. Y dos
negativas explícitas: si la vigencia no se puede resolver se comprueba **todo** y se dice por
@@ -620,7 +620,7 @@ perfil `anfitrion-cards` fallaban por `MEMCG`. **Estado tras pagar H1 (2026-08-3
cumplen**, y los 10 sellados que quedaron atrás salen listados como superados. El kernel vivo de
esta máquina pasa las 11 exigidas.
Lo que **sí** funciona en un kernel hammer: `cpu.weight` (CGROUP_SCHED), `pids.max`
Lo que **sí** funciona en un kernel takana: `cpu.weight` (CGROUP_SCHED), `pids.max`
(CGROUP_PIDS), `cpu.stat` (rstat del core) y `cgroup.freeze` — este último porque el freezer
v2 es `obj-y` en `kernel/cgroup/Makefile`, no el `CONFIG_CGROUP_FREEZER` de v1.
@@ -644,7 +644,7 @@ v2 es `obj-y` en `kernel/cgroup/Makefile`, no el `CONFIG_CGROUP_FREEZER` de v1.
**Ninguna de estas exige un módulo, un parche ni un kernel propio.** Es la primera conclusión
del documento y la que ordena todo lo demás.
## 6. Qué exigiría tocar el kernel — y su precio en hammer
## 6. Qué exigiría tocar el kernel — y su precio en takana
Después de agotar §5 quedan tres deseos legítimos que mainline no da:
@@ -665,10 +665,10 @@ Su precio, concreto:
del toolchain»*). Deuda ya medio pagada: dwarves construye (frente libdw/musl cerrado).
- **El bloqueo de diseño, que no es técnico**: `SDD-SERVIDOR-AJENO.md` promete *«cero
invasión — la máquina sigue siendo su Ubuntu de siempre»*. Un sandokan que dependa de un
kernel hammer deja de correr ahí. ⇒ **el núcleo se queda portable siempre; lo rápido es un
kernel takana deja de correr ahí. ⇒ **el núcleo se queda portable siempre; lo rápido es un
backend opcional detectado en runtime, con `/proc` como respaldo que no se borra.**
## 7. Propuestas — lado hammer
## 7. Propuestas — lado takana
- **H1 · `-e MEMCG -e PSI` en los kernels que hospedan Cards. PAGADO 2026-08-30.** Cierra §4 y
habilita `memory.current` (T4) y las señales de presión. Se hizo en **una sola tanda** —el precio
@@ -685,7 +685,7 @@ Su precio, concreto:
2. **Precio en bytes, medido**: bzImage `linux-generic` 16 856 064 → 16 937 984 (**+80 KiB,
+0,49%**), `linux-metal` +84 KiB (+0,57%), `linux-metal-dual` +88 KiB (+0,53%), `linux-gioser`
+80 KiB (+0,71%). Contabilidad por unidad y presión, por medio punto porcentual de kernel.
3. **La prueba de que se pagó es el mismo comando**: `hammer kernel contract --sealed` sale 0 y
3. **La prueba de que se pagó es el mismo comando**: `takana kernel contract --sealed` sale 0 y
dice `5 de 5 config(s) VIGENTES cumplen su perfil`. La salida cruda quedó en
`docs/evidencia/tasas-kernel-2026-08-29/contrato-h1-pagado.txt`, junto con el `diff-back` del
plan de gioser sobre el config nuevo (35 cumplidos, 0 incumplidos).
@@ -695,13 +695,13 @@ Su precio, concreto:
decía «que harkaq use `openat2 RESOLVE_BENEATH` donde hoy valida rutas a mano (T9, ~gratis)».
Buscando ese sitio (2026-08-30) resultó que **harkaq no valida rutas a mano**: delega la
contención en Landlock, que es más fuerte que cualquier chequeo de ruta. Donde no se validaba
**nada** era en `hammer-build/hydrate.rs`.
**nada** era en `takana-build/hydrate.rs`.
**El fallo, reproducido**: hidratar es `target_fhs.join(rel)` y escribir por ruta ⇒ **un symlink
de directorio ya presente en el FHS se sigue**. Un artefacto que trae `usr/share/pkg → /algún/lado`
deja ese symlink puesto —los symlinks se replican literales, y así debe ser— y la hidratación
siguiente escribía `usr/share/pkg/archivo` **fuera del root, devolviendo `Ok(1)`**. Con
`hammer hydrate --into` sobre una imagen, eso es escribir en el sistema anfitrión diciendo que
`takana hydrate --into` sobre una imagen, eso es escribir en el sistema anfitrión diciendo que
todo fue bien: el fallo de CLAUDE.md §3, otra vez.
**Cuánto se estaba disparando: cero.** Del store: **160 symlinks absolutos, ninguno a un
@@ -715,9 +715,9 @@ Su precio, concreto:
temas de iconos), y hay un test para cada dirección en
`crates/hammer-build/tests/hidratacion_no_escapa.rs`.
**Y el mismo patrón estaba en el OTRO proyector**: `hammer-upgrade::project_plan` es
**Y el mismo patrón estaba en el OTRO proyector**: `takana-upgrade::project_plan` es
`target_root.join(rel)` + escribir por ruta, igual que hydrate. Con `--root /` no cambia nada
(todo empieza por `/`), pero `hammer upgrade apply --root /mnt/imagen` es el caso real de armar
(todo empieza por `/`), pero `takana upgrade apply --root /mnt/imagen` es el caso real de armar
una imagen: reproducido —la generación 2 escribía fuera del root con `ApplyReport` en verde— y
tapado con la misma regla (se mide el **directorio padre**, porque un `Replaced` sobre un symlink
que apunta afuera es legítimo: `rename` pisa el symlink, no escribe a través de él). Dos tests,
@@ -729,8 +729,8 @@ Su precio, concreto:
carrera es mover las escrituras a `openat2(RESOLVE_IN_ROOT)` + `linkat`/`renameat`, y arrastra el
camino de `patchelf` (que necesita una ruta, no un fd). Queda escrito acá con su precio, que es
más de lo que se sabía cuando H3 se llamaba «~gratis».
- **H4 · Ningún `Command` de hammer pone `pre_exec`** — Rust apaga el camino `posix_spawn` en
cuanto hay uno, y cae a `fork+exec` (T1: 17× a 256 MB tocados). `hammer-build/sandbox.rs` lanza
- **H4 · Ningún `Command` de takana pone `pre_exec`** — Rust apaga el camino `posix_spawn` en
cuanto hay uno, y cae a `fork+exec` (T1: 17× a 256 MB tocados). `takana-build/sandbox.rs` lanza
`bwrap` sin `pre_exec`, o sea que estaba bien **por suerte**: nadie lo comprobaba. **HECHO**: el
test `crates/hammer-build/tests/sin_pre_exec.rs` barre las fuentes del workspace y falla si
aparece uno (comprobado en los dos sentidos: inyectando un `pre_exec` el test se pone rojo, y un
@@ -815,7 +815,7 @@ Su precio, concreto:
la misma regla de §4.bis, aplicada un escalón antes. Salió `OK: CONFIG_DEBUG_INFO_BTF=y`, que es
lo que paga la parte (3): ese pahole estático **se encuentra y se ejecuta dentro del sandbox**.
El artefacto de medición **se borró a propósito** tras anotar los números: `hammer kernel
El artefacto de medición **se borró a propósito** tras anotar los números: `takana kernel
contract --sealed` lo listaba como SIN COMPROBAR —no declara `[[target]]`, que es justo lo que
exige H8— o sea un ⚠ permanente en `docs/state/kernel-contract.txt`, que el cron commitea cada
30 min. Un aviso fijo que no corresponde a ningún problema es exactamente cómo se deja de leer un
@@ -866,7 +866,7 @@ Su precio, concreto:
**Lo que queda abierto, con su precio:** montar el overlay con la API nueva (`fsopen`+`fsconfig`
con `lowerdir+` repetido) borra el muro —el banco monta así las 64 capas reales que `mount(2)`
rechaza— y con él la necesidad de `.dmerge`. El precio no es el código de hammer: **es bwrap**,
rechaza— y con él la necesidad de `.dmerge`. El precio no es el código de takana: **es bwrap**,
que monta el overlay dentro de su namespace con la API vieja. O se parchea bwrap, o el overlay se
monta fuera y bwrap lo entra con `--bind`, lo que mueve el montaje al lado privilegiado. No se
decide acá; se deja medido que la puerta existe.
@@ -896,7 +896,7 @@ En una línea cada una, como se propusieron:
`waitpid(WNOHANG)` en sondeo). Se gana correctitud (carrera de PID) antes que velocidad.
- **W2 · Telemetría por unidad desde el cgroup**, no sumando `/proc` (T4). Con **sonda de
capacidad**: si `memory.current` no existe (kernel sin MEMCG), decirlo, no callarlo. Del lado
hammer esa sonda ya tiene con qué contrastarse: `hammer kernel contract --list --json` publica
takana esa sonda ya tiene con qué contrastarse: `takana kernel contract --list --json` publica
qué promete cada perfil de kernel (§4.ter), que es el W6 en formato de dato.
- **W3 · `arje-incarnate` con `posix_spawn`** donde el `pre_exec` no sea imprescindible (T1),
y `close_range` en el que quede.
@@ -904,8 +904,8 @@ En una línea cada una, como se propusieron:
límite no aplicado son el bug de §4, no un detalle de estilo.
- **W5 · `/proc` no se borra nunca**: es el respaldo portable que hace posible
SDD-SERVIDOR-AJENO.
- **W6 · El contrato entre repos es el trait `Engine`, no una ABI de kernel.** hammer publica
la lista de símbolos garantizados (verificable con `hammer kernel probe`); tawasuyu elige
- **W6 · El contrato entre repos es el trait `Engine`, no una ABI de kernel.** takana publica
la lista de símbolos garantizados (verificable con `takana kernel probe`); tawasuyu elige
backend en runtime.
## 9. Los experimentos nombrados — cinco medidos, uno abierto
@@ -1001,7 +1001,7 @@ Dos lecturas:
### 9.5 · Iterador BPF contra `/proc` — MEDIDO (`bench_bpf_iter.c` + `bpf_iter_task.bpf.c``bpf-iter.txt`)
Medido el **2026-08-31**, sobre el kernel de desarrollo (Artix 7.1.4, que sí trae BTF) —
**no sobre un kernel de hammer, que es justamente lo que está en discusión**. Tercera corrida,
**no sobre un kernel de takana, que es justamente lo que está en discusión**. Tercera corrida,
condiciones propias: **sus números no se cruzan con los de §2 ni con los de §9.1-9.4.**
Los dos caminos hacen **el mismo trabajo**: sacar `{pid, ppid, estado, utime, stime, comm}` de
@@ -1091,7 +1091,7 @@ Se compilan igual que los otros (`bench_sqpoll` pide `-luring`) y los tres prime
`CAP_SYS_NICE`.
**Contrato de capacidades**: `docs/state/kernel-contract.toml` (el dato) y
`hammer kernel contract --list` (la vista). Los ficheros de esta carpeta no cambian; el contrato sí
`takana kernel contract --list` (la vista). Los ficheros de esta carpeta no cambian; el contrato sí
—cada vez que un consumidor nuevo usa el kernel por nombre.
**Tercera corrida (§9.5, iterador BPF, 2026-08-31)**: `bpf_iter_task.bpf.c` (el programa BPF) +
+7 -7
View File
@@ -95,17 +95,17 @@ vez de suponerlo:**
- `.dev-fs/alpine` **ya trae clang22 + llvm22 22.1.8** — la misma major que usa el APKBUILD de
Alpine para este mismo Firefox (`_llvmver=22`).
- **`Compiler::Clang` ya existe en hammer**, cableado de punta a punta (`parse_compiler` lo acepta;
`hammer-build` pone `CC=clang`, `CXX=clang++`, `AR=llvm-ar`). Ninguna receta lo usaba.
- **`Compiler::Clang` ya existe en takana**, cableado de punta a punta (`parse_compiler` lo acepta;
`takana-build` pone `CC=clang`, `CXX=clang++`, `AR=llvm-ar`). Ninguna receta lo usaba.
- Faltaba **sólo `ld.lld`**: `apk add lld` ⇒ dos paquetes, cero upgrades.
Y los tres muros caen sin perder lo que gcc daba: el sondeo de linker se satisface con
`--enable-linker=lld`, el `ar` lo pone hammer solo, y el `NEEDED` de la stdlib de C++ existe porque
`--enable-linker=lld`, el `ar` lo pone takana solo, y el `NEEDED` de la stdlib de C++ existe porque
clang++ de Alpine usa la `libstdc++` **compartida** — la prueba no es teórica: Alpine construye este
Firefox con clang22 y **sin** `libcxx` en sus makedepends.
**La huella del lab no se movió, y se midió antes de tocar nada.** `lld` no casa ningún prefijo de
`TOOLCHAIN_PREFIXES` (`hammer-core/src/lab.rs`), así que los 43 paquetes que entran en `hash_inputs`
`TOOLCHAIN_PREFIXES` (`takana-core/src/lab.rs`), así que los 43 paquetes que entran en `hash_inputs`
salieron idénticos ⇒ **los 837 artefactos sellados quedan intactos**. Eso abarata el cambio hoy y a
la vez deja un agujero escrito: la versión de `lld` no es parte de la identidad del artefacto, y sólo
expone a las recetas `compiler="clang"` — hoy, una. Cerrarlo cuesta re-hashear el corpus entero.
@@ -169,7 +169,7 @@ y el scope de addons se juntan en una sola pasada, no en seis.
**El perfil se junta corriendo el navegador, y las dos distros lo hacen bajo `xvfb-run`** (Alpine:
`xvfb-run -a -s "-screen 0 1920x1080x24" ./mach python build/pgo/profileserver.py`; Arch, idéntico).
**Nosotros no tenemos X11 en el corpus** —es la decisión Wayland-only de toda la distro— así que ese
camino no existe acá. La salida hammer-nativa es correrlo bajo un **sway headless**
camino no existe acá. La salida takana-nativa es correrlo bajo un **sway headless**
(`WLR_BACKENDS=headless`), que ya tenemos del frente wlr/sway y arranca en segundos.
**✅ EL MURO DEL DISPLAY ESTÁ DESPEJADO (2026-09-06).** No es una previsión: se corrió. `sway`
@@ -488,7 +488,7 @@ La columna «quién más lo tiene» es lo que evita que nos contemos un cuento.
| # | Qué | Quién más lo tiene | Pieza que ya existe | Costo |
|---|---|---|---|---|
| 6.1 | **`sct` — transparencia de scripts** | **nadie** | `puriy-sct`, `puriy-sct-testigo` | medio |
| 6.2 | **Descargas direccionadas por contenido** | nadie | store CAS de hammer, `tejido` | bajo |
| 6.2 | **Descargas direccionadas por contenido** | nadie | store CAS de takana, `tejido` | bajo |
| 6.3 | **Archivo personal + RAG local** | Rewind/Recall (nube, Windows); SingleFile (guarda, no busca) | `khipu`, `rag-motor`, `willay-rag` | medio |
| 6.4 | **Historial y perfil sobre `qullqa`** | nadie | `qullqa-core`, `qullqa-pozo` | medio |
| 6.5 | **Foco por `cortafuegos`, no por extensión** | nadie (nadie es dueño del navegador *y* del sistema) | `cortafuegos`, `pacha` | bajo |
@@ -973,7 +973,7 @@ siguiente.
| # | Unidad | Puerta que abre | Bloqueada por |
|---|---|---|---|
| 1 | **`waterfox` SELLA** ✅ 2026-09-06 — `b3:88b5a762`, 377 M, BuildID determinista **y REPRODUCE bit a bit**. Cuatro muros, **ninguno de la receta**: hammer viejo en el worker, el watchdog barriendo el post-mortem, deps estáticas vs. el cairo bundleado, y una rotura de upstream sólo-Linux | valida la tesis del reúso | — |
| 1 | **`waterfox` SELLA** ✅ 2026-09-06 — `b3:88b5a762`, 377 M, BuildID determinista **y REPRODUCE bit a bit**. Cuatro muros, **ninguno de la receta**: takana viejo en el worker, el watchdog barriendo el post-mortem, deps estáticas vs. el cairo bundleado, y una rotura de upstream sólo-Linux | valida la tesis del reúso | — |
| 2 | ~~`llvm-toolchain`~~**`apk add lld` + `compiler="clang"`** ✅ 2026-09-05 | LTO **y** el muro 3 | — |
| 3 | Construir el `firefox` con LTO (hash `b3:6f2a3b2f`) | el eje de velocidad **y** las extensiones de `atuq` | 2 ✅ |
| 3.a | **PGO ENCENDIDO** ✅ 2026-09-07 — perfil de 501.521 funciones recogido bajo headless, publicado en el mirror y pineado por sha256; 189 compilaciones con `-fprofile-use`. Sin `jarlog` todavía | la otra mitad de la ganancia | 3 ✅ |
+47 -47
View File
@@ -1,7 +1,7 @@
# hammer — informe de arquitectura completo (briefing para un agente externo)
# 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 hammer al nivel de proponer SDDs nuevos: arquitectura, capas,
> 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.
>
@@ -17,7 +17,7 @@
## 0. Resumen ejecutivo en diez líneas
`hammer` es una **distribución Linux + su gestor de construcción**, escrita en Rust, cuya tesis es
`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
@@ -61,7 +61,7 @@ yo.»* Determinismo en la fábrica, libertad en la ejecución; ninguno de los do
humano dispone · no reinventar lo resuelto · **el diario es la verdad** (se vive imperativamente y
el estado declarativo se deriva después, nunca al revés).
**Anti-objetivos.** No es un gestor inmutable; no reescribe un motor de build (ADR 0004: **hammer
**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.
---
@@ -76,7 +76,7 @@ NO usa nix**); no compila contra `HEAD` vivo (ADR 0006); no oculta complejidad t
upstreams (commits/tarballs FIJADOS)
hammer-build (bwrap + zig cc) ──sella──▶ CAS store ──hidrata──▶ /bin /lib /etc
takana-build (bwrap + zig cc) ──sella──▶ CAS store ──hidrata──▶ /bin /lib /etc
recetas + grafo de deps /store/<b3>-<name>/ (hardlinks)
│ │
▼ ▼
@@ -95,16 +95,16 @@ store + hidratación); el userland jamás compila contra el host (delega al lab)
| Crate | Responsabilidad única |
|---|---|
| `hammer-core` | Tipos compartidos: `Recipe`, `Swm`, `Store`, `ArtifactHash`, `proto` (bus), `repo`, `sign`, `installed`, `compat` (slots), `query`, `differs`, `lab`, `apply`, `caps`, y el módulo `kernel/` (SDD 22) |
| `hammer-build` | El laboratorio: `sandbox` (bwrap), `fetch`, `download`, `hydrate`, `swm_bridge`, `harkaq` (la jaula), `config` |
| `hammer-bootstrap` | Orquesta stage0/stage1/stage2/builder/all + `manifest` (log de transparencia) |
| `hammer-overlay` | `try`/`commit`/`discard`/`status` sobre overlayfs, con apilamiento LIFO |
| `hammer-journal` | Diario append-only JSON-líneas, `content_hash`, de-dup idempotente |
| `hammer-mirror` | Replicación del store por hash entre máquinas (verifica `of_tree` al recibir) |
| `hammer-upgrade` | Generaciones, apply/rollback/prune/recover, `boot_graph` (ADR 0010) |
| `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 |
| `hammer-agent` | `AgentClient`, `IntentTranslator` (+ `MockTranslator`, `ClaudeTranslator`), `Orchestrator` |
| `hammer-cli` | El binario `hammer` (+ `alpine_import`, `nix_import`, `kernel_cmd`) |
| `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 |
@@ -125,7 +125,7 @@ store + hidratación); el userland jamás compila contra el host (delega al lab)
### 3.1 `Recipe` — el átomo del grafo
TOML declarativo y puro. Campos reales (`hammer-core/src/recipe.rs`):
TOML declarativo y puro. Campos reales (`takana-core/src/recipe.rs`):
```toml
name = "grep"
@@ -179,7 +179,7 @@ Consecuencias que gobiernan casi todas las decisiones del proyecto:
- **Las fases de build SÍ entran.** ⇒ editar el `-j` de una fase cambia el hash aunque el binario
sea idéntico; ⇒ el `.config` del kernel (que vive en la fase `configure`) **es la identidad del
artefacto** (SDD 22 §1); ⇒ `strip_debug` obliga a re-hashear el corpus (SDD 23).
- **El hash es de entrada, no de contenido.** `hammer hash <receta>` responde en ~2 ms cuál es el
- **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.
@@ -223,7 +223,7 @@ de mutación: `source_patch` (una `Recipe` serializada para viajar), `config_edi
sin fuzz), `init_rule` y `file_drop` (`content_b64` XOR `content_url`, siempre con `content_hash`
BLAKE3 verificado **antes** de escribir nada).
`hammer apply` verifica base → verifica firma → **reproduce** en el lab local → aísla en overlay →
`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)
@@ -291,7 +291,7 @@ honesto sobre el ABI (kernel sin Landlock ⇒ build marcado *sin evidencia*, nun
denegado; si no aparece, el lector está roto).
Estado: Fase 0 y Fase 1 cerradas (`zlib` real: `configure→Hermetico`, `compile→Impuro` por
`/usr/bin/make` no declarado); wiring en `hammer-build/src/harkaq.rs` detrás de `HARKAQ=1` e
`/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
@@ -325,13 +325,13 @@ intención NL → PLAN (.swm) → BUILD (lab) → TRY (overlay) → VERIFY (test
→ PROPOSE (diff + evidencia) → HUMANO decide commit/discard
```
`hammer ai <intent> --catalog F | --llm [--repair-max-attempts N]`. El traductor está detrás del
`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** (`hammer query kind:value`): `bin`, `file`, `pin`, `service`,
**Mini-lenguaje de consulta** (`takana query kind:value`): `bin`, `file`, `pin`, `service`,
`depends` — con parser ELF64 mínimo para extraer `DT_NEEDED`.
---
@@ -343,7 +343,7 @@ fuente pública (repo@commit fijado) + patch declarado
→ build determinista en TU lab local → artifact_hash
→ ¿coincide con el expected_hash del autor?
sí → obtuviste su binario sin descargarlo
no → algo difiere (pin, patch, toolchain); hammer dice qué
no → algo difiere (pin, patch, toolchain); takana dice qué
```
La firma Ed25519 cubre **autoría e integridad**, no autoriza promover: `apply` **siempre** reproduce
@@ -373,12 +373,12 @@ Hitos verificados:
**`CRASHED` real** que la Fase 5 había diferido está demostrado: `kill hammerd` → arje detecta
`Killed(SIGTERM)`, aplica backoff y re-encarna.
- **Stage 2 `✓ REPRODUCIBLE`** host↔VM: `of_tree(stage1') == of_tree(stage1)`.
- **Auto-alojamiento puro (variante b) CERRADO**: el toolchain del builder lo construye hammer desde
- **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 hammer bootea la VM del
- **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.
@@ -397,9 +397,9 @@ Hitos verificados:
- **E1 imagen auto-booteable** (GRUB BIOS i386-pc, instalado en userspace sin root con
`grub-mkimage` + `grub-bios-setup` sobre el fichero imagen). Layout GPT: BIOS-boot / `/` /
`/store` / `/var/lib/hammer`.
- **E2 instalador a disco físico**: `hammer-install <dev>` corre dentro del live como root real
- **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** (`hammer mirror push|pull|status`): CAS replicado; el receptor
- **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,
@@ -407,7 +407,7 @@ Hitos verificados:
(roll-forward) o deshaga (roll-back). `hammer-recover` (binario estático) lo hace **al arranque**,
antes de encarnar a arje-zero, y nunca aborta el boot.
- **E5 ISO/medio live**: híbrido BIOS+UEFI (dos El Torito), initramfs = product-rootfs `cpio.gz`,
todo desde RAM. **Dogfooding**: el host no traía `xorriso` ni `mtools` ⇒ los construye hammer.
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.
@@ -416,21 +416,21 @@ Hitos verificados:
## 9. Arranque por grafo (ADR 0010) — la frontera con `tawasuyu`/mirada
El menú de arranque **no es una lista de kernels**: es la **navegación del grafo de estados** del
sistema. hammer publica `/run/hammer/boot-graph.json` (nodos = generaciones/snapshots/base/recovery,
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 `hammer boot activate --from-select` **incondicionalmente en su secuencia de apagado**`/run`
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
`hammer boot menu` es sólo harness de dev).
`takana boot menu` es sólo harness de dev).
---
## 10. El armador de kernel (SDD 22) — implementado
Verbo `hammer kernel {stats, probe, bundles, closure, plan, diff-back, gate, hw}`.
Verbo `takana kernel {stats, probe, bundles, closure, plan, diff-back, gate, hw}`.
Los dos hechos que ordenan el diseño:
@@ -460,7 +460,7 @@ graba `CONFIG_GCC_VERSION`/`CONFIG_RUSTC_VERSION` en el `.config`.
| Frente | Estado | Notas clave |
|---|---|---|
| **mirada** | perfil 33/33 *(medido)* | Compositor Wayland propio (tawasuyu) + greeter, sobre mesa software o iris |
| **KDE Plasma 6** | perfil `escritorio-kde` **163/163** *(medido)* | Hidratado rebuild-free vía `hammer hydrate <hash> --into` (install-reproduce no escala). ADR 0011 |
| **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 `dlopen`ea `.so` reales ⇒ la introspección los exige. `mutter→gnome-shell` es HUB-ONLY estructural |
| **COSMIC** | perfil 89/90 *(medido)* | 4º escritorio usable, 6 apps + panel/dock, portal cerrado (captura da PNG real). Barato: no hay torre de C debajo |
| **wlr/sway** | perfil `escritorio-sway` **129/129** *(medido)*, arranca en QEMU | 10 recetas en una noche: wlroots, sway, yambar, fuzzel, swaybg/lock/idle, grim, slurp, wl-clipboard |
@@ -613,7 +613,7 @@ Medido a escala: al invalidar `libdrm`, ~93 de 205 recetas KDE murieron con `/sr
full path to an existing compiler tool` — el wrapper de zig que la receta deja EN EL ÁRBOL, barrido
por el fetch concurrente de otra receta.
Mitigación vigente (no solución): **`flock work/.farm-build.lock` alrededor de todo `hammer build`**,
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)
@@ -623,7 +623,7 @@ caché inmutable + copia.
92 paquetes con no-determinismo probable. La causa medida **no** son rutas del host sino **rutas
internas al árbol de build que varían entre corridas** (`/src/output/meson-private`). Verificado con
`hammer why-differs`: `binutils` y `wl-clipboard` reproducen; `appstream` y `bison` **divergen**, y
`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
@@ -662,7 +662,7 @@ corpus (§13.3).
### 14.4 Modos de fallo estructurales que este proyecto descubrió (y que valen como principio)
1. **Un artefacto VACÍO es un cache-hit.** `hammer build` miraba presencia del directorio, no
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.*
@@ -689,9 +689,9 @@ corpus (§13.3).
### 15.1 SDD 15 — proof-carrying, cómputo como dato, código por contenido
- **H1 — Proof-carrying recipes ✅**: el `.swm` lleva un bloque `evidence` (`{kind, cmd, expected}`,
`kind ∈ {cmd-exit, proptest, contract, kani}`); `hammer swm-verify --evidence` corre cada ítem
`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: `hammer-agent` no depende de `hammer-build`); `Event::BuildReady` lleva el
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,
@@ -707,7 +707,7 @@ corpus (§13.3).
duro: requisitos resueltos + reclamos disjuntos. Dos casos que el modelo trata distinto: **logo**
(dos configs escriben la misma superficie ⇒ **colisión = elección**) y **wayland** (dependencia
que no resuelve ⇒ **rechazo**). Subido a la receta real (`[slots]`, fuera de `hash_inputs`) y a la
`InstalledDb` (`system_state() → slot → hash`). CLI: `hammer compat`.
`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.
@@ -715,7 +715,7 @@ mismo tipo de objeto direccionado por el mismo hash.
### 15.2 SDD 17 — los diez cierres de frontera, priorizados
**Tesis:** los tres invariantes ya pagados —**bit-reproducibilidad**, **direccionamiento por
contenido** y **clausura-como-política**— hacen casi gratis para hammer lo que a otros les cuesta.
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
@@ -732,15 +732,15 @@ La búsqueda correcta no es «qué frontera agregar» sino **«qué cae solo del
que no son paths y no caben en ninguna jaula por-fichero, y el build offline no la contiene ⇒
**frontera** (clases `dns`/`tls-certs`/`random`/`locale-tz`/`syslog`, **bits de un `u32`
firmado** de 36 bytes canónicos) + **detalle** (paths, Landlock, medidos).
4. **CVE por grafo**`hammer affected CVE-X` exacto + frontera mínima de rebuild + hydrate para
repartir el parche rebuild-free. Donde Debian/Alpine aproximan por nombre-versión, hammer
4. **CVE por grafo**`takana affected CVE-X` exacto + frontera mínima de rebuild + hydrate para
repartir el parche rebuild-free. Donde Debian/Alpine aproximan por nombre-versión, takana
responde exacto.
5. **Sellar el arranque** — verity + medida; el store CAS hace fs-verity casi gratis.
6. **Matar el trusting-trust: DDC** — con la cadena mrustc cerrada, hammer es de los poquísimos que
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. **`hammer oci`** — imágenes distroless bit-reproducibles desde un closure. Vector de adopción
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
@@ -787,14 +787,14 @@ pero **falta `EROFS_FS` y `FS_VERITY`** ⇒ re-sellado de kernel.
| 0007 | `arje` como init propio del track posterior | propuesta (implementada de facto) |
| 0008 | Bootstrap en 3 stages, `zig` como semilla | aceptada |
| 0009 | Código direccionado por contenido (Unison): registrar la visión, no reescribir | propuesta / design-doc |
| 0010 | Arranque por grafo: el menú de boot como navegación del grafo | aceptado; lado hammer 15 ✅ |
| 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 (`hammer`)
## 17. Superficie del CLI (`takana`)
```
build · hash [--check] · why-differs · attest · hydrate
@@ -816,9 +816,9 @@ Daemon: `hammerd [--journal DIR] [--agent-caps FILE]`.
## 18. Reglas del repo (CLAUDE.md) — no son estilo, son seguridad
1. **Todo `hammer build` va envuelto en `flock work/.farm-build.lock`.** Por el ADR 0012. Para una
tanda, tomar el lock **una sola vez**, no por receta. El lock está **fuera** de `hammer build` a
propósito: los scripts de granja lo toman por fuera y hammer se bloquearía contra ellos.
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.
+2 -2
View File
@@ -1,4 +1,4 @@
# Documentación de diseño de hammer
# Documentación de diseño de takana
Estos documentos son la fuente de verdad del diseño. El código los implementa; cuando haya
discrepancia, o se corrige el código o se actualiza el SDD con un commit que explique por qué.
@@ -20,7 +20,7 @@ discrepancia, o se corrige el código o se actualiza el SDD con un commit que ex
| 10 | [Roadmap](10-roadmap.md) | Fases, MVP, primer entregable |
| 11 | [Bootstrap from-scratch](11-bootstrap.md) | Track posterior: Stage 0/1/2, auto-alojamiento, semilla pinned |
| 12 | [`arje` como init real del Stage 1](12-init-real.md) | Contrato de runtime: seed card, hammerd supervisado, `CRASHED` real |
| 16 | [harkaq: la jaula de hammer](16-harkaq-jaula.md) | Landlock+seccomp sobre el bwrap actual; política = clausura; evidencia negativa de hermeticidad |
| 16 | [harkaq: la jaula de takana](16-harkaq-jaula.md) | Landlock+seccomp sobre el bwrap actual; política = clausura; evidencia negativa de hermeticidad |
| 26 | [`atuq`: el envoltorio Gecko](26-atuq-envoltorio-gecko.md) | Navegador propio como artefacto DERIVADO de `firefox` (no fork de fuente); la toolchain clang como puerta de PGO/LTO; qué se promete y qué no |
### Runbooks (operativos)
+1 -1
View File
@@ -24,7 +24,7 @@ binarios que deben correr en un userland musl, posiblemente estáticos.
## Consecuencias
- Curva de entrada más alta que Go; asumida.
- Compilación del compilador del lab es **ortogonal**: escribimos `hammer` en Rust, el lab
- Compilación del compilador del lab es **ortogonal**: escribimos `takana` en Rust, el lab
invoca `zig cc` para compilar C/C++ upstream. Sin conflicto (ver [ADR 0003](0003-zig-cc-builder.md)).
- Perfil `release` ya configurado para binarios pequeños (`opt-level=z`, `lto`, `strip`,
`panic=abort`).
+1 -1
View File
@@ -12,7 +12,7 @@ build determinista + mutable + diario + `.swm`.
## Decisión
Montar y validar toda la capa de `hammer` **sobre Alpine Linux** (Fases 06). Sólo después,
Montar y validar toda la capa de `takana` **sobre Alpine Linux** (Fases 06). Sólo después,
una vez probado el concepto, bajar a la distro propia (track posterior).
## Razones
+2 -2
View File
@@ -12,14 +12,14 @@ derivaciones es el núcleo de nuestra fábrica.
## Decisión
**No** construir un motor de build genérico tipo Nix desde cero. Implementamos lo **mínimo**
que necesita `hammer`: un builder con recetas explícitas, sandbox `bubblewrap`, hashing CAS
que necesita `takana`: un builder con recetas explícitas, sandbox `bubblewrap`, hashing CAS
BLAKE3 de entradas conocidas, y un DAG simple de dependencias declaradas. Nada de un lenguaje
funcional, evaluación perezosa, ni resolución general de paquetes.
## Razones
- Un motor de grafo hermético con caché correcta es, por sí solo, un proyecto de **años**.
No es donde está el valor de `hammer`.
No es donde está el valor de `takana`.
- Nuestro valor es el **pegamento AI-nativo** (overlay + diario + `.swm` + bus), no reescribir
teoría de build ya resuelta.
- El alcance mínimo (recetas explícitas + CAS + DAG) cubre las Fases 06 sin esa complejidad.
+1 -1
View File
@@ -27,7 +27,7 @@ vivo" significa **rastreo automatizado del upstream con snapshots fijados**, no
## Mecanismo de "vivo"
- `pins.toml` mapea `nombre → commit` por upstream.
- `hammer update [<pkg>]` consulta el upstream, propone subir el pin al nuevo commit, y
- `takana update [<pkg>]` consulta el upstream, propone subir el pin al nuevo commit, y
reconstruye sólo lo afectado. El humano (o la IA) decide cuándo avanzar.
- Análogo al `flake.lock` de Nix, pero imperativo y bajo tu control.
+13 -13
View File
@@ -14,30 +14,30 @@ en Rust con PID 1 (`arje-zero`), supervisión real de servicios (`RestartTracker
(`arje-bus`, postcard, `SO_PEERCRED`), CAS (`arje-cas`), snapshot (`arje-snapshot`), runtime WASM
(`arje-soma`+`arje-wasm`) y absorción de sistemas existentes (`arje-absorb`).
Al revisar arje contra hammer aparece que **ambos convergieron en los mismos primitivos** por
Al revisar arje contra takana aparece que **ambos convergieron en los mismos primitivos** por
caminos distintos: CAS direccionado por contenido, bus de agente sobre socket Unix con
`SO_PEERCRED`, firma Ed25519, y el principio "la IA propone, el humano dispone". Mantenerlos
separados duplicaría bus, CAS y modelo de confianza.
## Decisión
1. **arje es el init del track posterior de hammer.** No se escribe un init nuevo. arje aporta
1. **arje es el init del track posterior de takana.** No se escribe un init nuevo. arje aporta
PID 1 + supervisión, lo que **entrega el `CRASHED` real** que la Fase 5 dejó diferido.
2. **Frontera de responsabilidades (PID 1 fino):**
- **arje** = boot, PID 1, supervisión/restart, mount del overlay, snapshot, CAS, atestación
al arranque.
- **hammer** = laboratorio de build determinista, diario de mutaciones (`fanotify`), `.swm`
- **takana** = laboratorio de build determinista, diario de mutaciones (`fanotify`), `.swm`
reproducible + firma, bucle agéntico con IA. `hammerd` corre como **servicio supervisado
por arje**.
3. **Un solo bus.** El control de ciclo de vida de `hammerd` va por `arje-bus`. El `agent.sock`
(JSON-líneas) de hammer queda como **API de IA de alto nivel encima**, no como segundo plano
de control del init. `hammer-core::proto` se comparte; el transporte es `arje-bus`.
4. **CAS unificado en BLAKE3.** hammer ya usa BLAKE3 (prefijo `b3:`). arje migra `arje-cas` de
(JSON-líneas) de takana queda como **API de IA de alto nivel encima**, no como segundo plano
de control del init. `takana-core::proto` se comparte; el transporte es `arje-bus`.
4. **CAS unificado en BLAKE3.** takana ya usa BLAKE3 (prefijo `b3:`). arje migra `arje-cas` de
SHA-256 a BLAKE3 para hablar el mismo hash. El store pasa a ser un crate compartido.
5. **Modelo de confianza en capas.** hammer **garantiza procedencia** (reproducir desde fuente
5. **Modelo de confianza en capas.** takana **garantiza procedencia** (reproducir desde fuente
pública → `expected_hash` BLAKE3). arje/agora **atesta autorización** (`verificar_capacidad`
sobre `(blake3, permisos)` al boot). **El `expected_hash` de un `.swm` ES el BLAKE3 que arje
atesta.** Un `hammer commit` promovido emite una concesión firmada que `arje-absorb` integra
atesta.** Un `takana commit` promovido emite una concesión firmada que `arje-absorb` integra
al seed → el binario mutado por la IA queda atestado en el próximo arranque.
## Razones
@@ -52,15 +52,15 @@ separados duplicaría bus, CAS y modelo de confianza.
## Consecuencias
- **Track posterior** deja de necesitar "init propio from-scratch": adopta arje. El bootstrap
Stage 0/1 (toolchain semilla) sigue siendo de hammer; el init lo pone arje.
- hammer asume **BLAKE3** como hash único del store (ya lo es) y arje se alinea.
- Surge una dependencia de coordinación entre repos (`hammer``tawasuyu/03_ukupacha/arje`):
el wire de `arje-bus` y `hammer-core::proto`, y el formato de la concesión de capacidad.
Stage 0/1 (toolchain semilla) sigue siendo de takana; el init lo pone arje.
- takana asume **BLAKE3** como hash único del store (ya lo es) y arje se alinea.
- Surge una dependencia de coordinación entre repos (`takana``tawasuyu/03_ukupacha/arje`):
el wire de `arje-bus` y `takana-core::proto`, y el formato de la concesión de capacidad.
## Caveat estratégico
arje también es *"the natural bootloader for `wawa-kernel`"* (un SO nativo sin glibc/TCP-IP). La
**cage glibc** para software propietario (Steam/NVIDIA) es feature del mundo **hammer/Linux**, no
**cage glibc** para software propietario (Steam/NVIDIA) es feature del mundo **takana/Linux**, no
de arje core — arje declara de sí mismo *"for boot, not for governing the running system"*.
Mantenerla fuera de PID 1 preserva tanto el init fino como el norte nativo de wawa.
+1 -1
View File
@@ -5,7 +5,7 @@
## Contexto
El track posterior ([SDD 11](../11-bootstrap.md)) baja de "hammer sobre Alpine" a "hammer sobre
El track posterior ([SDD 11](../11-bootstrap.md)) baja de "takana sobre Alpine" a "takana sobre
sí mismo": compilar el sistema completo sin heredar el toolchain ni el userland de Alpine. Hay
que decidir **cómo se rompe el cordón umbilical** sin reintroducir la fragilidad del Stage 0
clásico (cross-toolchain a mano) que [ADR 0003](0003-zig-cc-builder.md) ya esquivó.
+9 -9
View File
@@ -7,9 +7,9 @@
## Contexto
hammer y wawa **ya** direccionan por contenido, pero *cosas distintas*:
takana y wawa **ya** direccionan por contenido, pero *cosas distintas*:
- **hammer** hashea el **artefacto compilado** y la **receta**. La identidad de un artefacto es
- **takana** hashea el **artefacto compilado** y la **receta**. La identidad de un artefacto es
`ArtifactHash` sobre las entradas canónicas de la receta (`Recipe::hash_inputs`,
`crates/hammer-core/src/recipe.rs:370`): `source_id` (commit git o sha256 del tarball),
compilador, target, link, `zig_version`, contenido de los patches, flags, overrides de fases.
@@ -21,17 +21,17 @@ Unison, el código no vive en archivos sino en una base content-addressed donde
ES su hash; renombrar es metadata, actualizar es insertar una definición nueva (la vieja no se
rompe: quien la referenciaba sigue apuntando a su hash). El sueño para un agente de IA: **no
editar archivos que se rompen entre sí**, sino insertar definiciones en un espacio donde
*nada se rompe por renombrar o actualizar*. Paquete de hammer, módulo wasm de wawa y función
*nada se rompe por renombrar o actualizar*. Paquete de takana, módulo wasm de wawa y función
de código **colapsarían en un solo espacio de nombres: el hash**.
## Decisión
**Registrar la visión y NO reescribir la unidad de compilación.** Concretamente:
1. **No** colapsar ahora artefacto/módulo/función en un solo tipo. hammer sigue con grano de
1. **No** colapsar ahora artefacto/módulo/función en un solo tipo. takana sigue con grano de
artefacto; wawa con grano de módulo. La identidad content-addressed de cada uno queda intacta.
2. **Sí** construir el *puente barato*: **procedencia por-símbolo como metadata**, derivada de lo
que ya sabemos leer, SIN tocar `hash_inputs` ni la unidad de build. hammer ya tiene un parser
que ya sabemos leer, SIN tocar `hash_inputs` ni la unidad de build. takana ya tiene un parser
ELF hand-rolled (`parse_elf_needed`, `crates/hammer-core/src/query.rs:298`) que extrae
`DT_NEEDED` (las libs dinámicas que un binario necesita) para la query `depends:`. Extenderlo
para emitir también los **símbolos exportados** (`.dynsym`) da, por artefacto, "qué provee y
@@ -45,7 +45,7 @@ de código **colapsarían en un solo espacio de nombres: el hash**.
## Razones
- **Es otro modelo de datos, no una extensión.** hammer content-addressa el artefacto compilado
- **Es otro modelo de datos, no una extensión.** takana content-addressa el artefacto compilado
(grano grueso, lenguaje-agnóstico); Unison el AST de cada función (grano fino, exige un
lenguaje y toolchain propios). Colapsarlos pide reescribir la unidad de compilación entera —
eso es un proyecto, no una fase. El ADR lo dice para no fingir que es una feature incremental.
@@ -57,7 +57,7 @@ de código **colapsarían en un solo espacio de nombres: el hash**.
ya: alimenta queries "¿qué artefacto exporta `foo`?", detección de colisiones de símbolos entre
paquetes, y una primera forma de "referencia por contenido" (un consumidor puede pinnear no el
paquete sino el *símbolo+hash* que usa). Todo reusando el parser ELF existente.
- **Respeta la disciplina de identidad de hammer.** Nada de esto mueve `ArtifactHash`: el
- **Respeta la disciplina de identidad de takana.** Nada de esto mueve `ArtifactHash`: el
baseline de reproducibilidad (of_tree 9adefb82 / rust 7fa6cb4e) no se toca. La procedencia es
metadata, como la evidencia de H1.
@@ -81,9 +81,9 @@ de código **colapsarían en un solo espacio de nombres: el hash**.
## Alternativas consideradas
1. **Adoptar Unison/su runtime tal cual.** Rechazada: arrastra un lenguaje y toolchain propios;
hammer es lenguaje-agnóstico por diseño (ADR 0002/0003, recetas sobre fuente upstream). Sería
takana es lenguaje-agnóstico por diseño (ADR 0002/0003, recetas sobre fuente upstream). Sería
cambiar la tesis del proyecto.
2. **Reescribir hammer para content-addressar AST de Rust.** Rechazada ahora: acopla la identidad
2. **Reescribir takana para content-addressar AST de Rust.** Rechazada ahora: acopla la identidad
del artefacto a un lenguaje concreto y a un parser de AST frágil; rompe "recetas sobre fuente
upstream en cualquier lenguaje". Reevaluable sólo si H3b da señal fuerte.
3. **No registrar nada (esperar).** Rechazada: la visión orienta decisiones de diseño cercanas
+25 -25
View File
@@ -1,14 +1,14 @@
# ADR 0010 — Arranque por grafo: el menú de boot como navegación del grafo de estados (mirada + hammer)
# ADR 0010 — Arranque por grafo: el menú de boot como navegación del grafo de estados (mirada + takana)
**Estado:** aceptado; lado hammer implementado (pasos 15 ✅). Instalador live EFI validado 2-stage en
**Estado:** aceptado; lado takana implementado (pasos 15 ✅). Instalador live EFI validado 2-stage en
OVMF ✅; cero-parpadeo validado por captura de framebuffer ✅ (salvo el *seamless* i915, metal-only).
Falta metal real y el lado mirada. **Fecha:** 2026-07-06 (impl. EFI-stub + cero-parpadeo 2026-07-10).
**Contexto cruzado:** hammer (arranque/kernel/generaciones) + tawasuyu/mirada (render). El
**Contexto cruzado:** takana (arranque/kernel/generaciones) + tawasuyu/mirada (render). El
espejo de este ADR para el lado mirada vive en `tawasuyu/HANDOFF-arranque-grafo.md`.
## Contexto
hammer quiere instalarse en metal real con un arranque **soberano**: nada de `systemd-boot`
takana quiere instalarse en metal real con un arranque **soberano**: nada de `systemd-boot`
(sigue siendo systemd) y GRUB-EFI está roto en este stack (GRUB 2.14 cuelga la rama UEFI, ver
[etapa-metal-usb]). El compositor **mirada** (tawasuyu, `02_ruway/mirada`) ya es "lo que ves al
arrancar": compositor Wayland + greeter + launcher sobre DRM/KMS. La idea del usuario: que el
@@ -16,7 +16,7 @@ arrancar": compositor Wayland + greeter + launcher sobre DRM/KMS. La idea del us
GRUB".
Al mirarlo, el menú no es un port de GRUB. En un Linux normal el menú es una **lista** de kernels.
En hammer, donde el sistema es un **grafo content-addressed** de estados (SDD 15: config = paquete =
En takana, donde el sistema es un **grafo content-addressed** de estados (SDD 15: config = paquete =
función = proceso), el menú puede ser la **navegación de ese grafo**: elegir un nodo = activar un
estado del sistema. Eso cierra el eslabón **proceso** que SDD 15 §H4 dejó abierto (las tres primeras
lentes ya coinciden en el hash; faltaba el lado proceso = replay de un log monótono de estados).
@@ -24,23 +24,23 @@ lentes ya coinciden en el hash; faltaba el lado proceso = replay de un log monó
## Decisión
El menú de arranque es un **selector gráfico sobre el grafo de estados del sistema**, renderizado por
**mirada** sobre KMS, respaldado por el modelo de generaciones/DAG de **hammer**. La frontera entre
los dos agentes es un **contrato de datos** (hammer describe el grafo; mirada lo dibuja y pide activar
**mirada** sobre KMS, respaldado por el modelo de generaciones/DAG de **takana**. La frontera entre
los dos agentes es un **contrato de datos** (takana describe el grafo; mirada lo dibuja y pide activar
un nodo). Invariante de UX compartido: **cero parpadeo** de DRM desde la firmware hasta el escritorio.
### Quién reside sobre qué
| Capa | Reside en | Notas |
|---|---|---|
| Cargador EFI + entrada en el disco instalado | **hammer** | install-to-disk EFI; ver "loader" abajo. |
| Kernel con KMS sin costura (simpledrm→i915) | **hammer** | `linux-generic.toml` ya trae `SIMPLEDRM+SYSFB_SIMPLEFB+FB_EFI+I915+FBDEV_EMULATION`. |
| El grafo de estados (nodos que el menú ofrece) + activar nodo | **hammer** | generaciones (hammer-upgrade/recover), overlay, DAG de wawa-memo. |
| Cargador EFI + entrada en el disco instalado | **takana** | install-to-disk EFI; ver "loader" abajo. |
| Kernel con KMS sin costura (simpledrm→i915) | **takana** | `linux-generic.toml` ya trae `SIMPLEDRM+SYSFB_SIMPLEFB+FB_EFI+I915+FBDEV_EMULATION`. |
| El grafo de estados (nodos que el menú ofrece) + activar nodo | **takana** | generaciones (takana-upgrade/recover), overlay, DAG de wawa-memo. |
| Render + navegación del menú sobre KMS | **tawasuyu/mirada** | reusa Prezi espacial / Zoom-Z (árbol fractal) / constelaciones. |
| Sostener el DRM master sin parpadeo menú→escritorio | **mirada** (con el kernel de hammer) | un solo framebuffer, abierto una vez, nunca re-modeset. |
| Sostener el DRM master sin parpadeo menú→escritorio | **mirada** (con el kernel de takana) | un solo framebuffer, abierto una vez, nunca re-modeset. |
### El contrato (la frontera limpia)
**hammer → mirada** — hammer escribe el grafo en un path bien conocido (propuesta:
**takana → mirada** — takana escribe el grafo en un path bien conocido (propuesta:
`/run/hammer/boot-graph.json`, regenerable, world-readable):
```json
@@ -62,14 +62,14 @@ un nodo). Invariante de UX compartido: **cero parpadeo** de DRM desde la firmwar
}
```
**mirada → hammer** — mirada pide activar el nodo elegido (propuesta: escribir el id en
`/run/hammer/boot-select` **o** invocar `hammer boot activate <id>`). hammer valida el id contra el
**mirada → takana** — mirada pide activar el nodo elegido (propuesta: escribir el id en
`/run/hammer/boot-select` **o** invocar `takana boot activate <id>`). takana valida el id contra el
grafo, activa la generación (pivota overlay/root) y sigue el arranque. Un nodo `recovery` mapea al
`hammer-recover --rollback` que ya existe.
> El grafo es content-addressed: cada `id` es un hash, `parents` forma un DAG (no una lista lineal).
> El modelo in-place actual de hammer (`hammer-recover`: "no hay menú de generaciones tipo NixOS")
> se **sube** a un grafo navegable — es trabajo nuevo de hammer, no un refactor cosmético.
> El modelo in-place actual de takana (`hammer-recover`: "no hay menú de generaciones tipo NixOS")
> se **sube** a un grafo navegable — es trabajo nuevo de takana, no un refactor cosmético.
### Cero parpadeo (invariante compartido)
@@ -102,18 +102,18 @@ vuelve a ser viable y soberano** — sin GRUB ni systemd. Es decir: squashfs+ove
es lo que *destraba* el arranque EFI soberano y de paso el cero-parpadeo (initrd chico = menos tiempo
muerto antes del takeover).
## Plan (lado hammer)
## Plan (lado takana)
1. **Este ADR + el handoff a tawasuyu** con el contrato (hecho en este commit).
2. **initrd chico → EFI-stub directo reprobado en OVMF** ✅ (2026-07-10): el disco instalado NO usa un
rootfs en RAM sino un **initramfs mínimo de pivote** (~770K) que resuelve `hammer-root` por LABEL y
rootfs en RAM sino un **initramfs mínimo de pivote** (~770K) que resuelve `takana-root` por LABEL y
hace `switch_root` a la ext4 real. Ese initrd chico es lo que destraba EFI-stub directo (el cuelgue
del metal era por un initrd de 100MB). Validado en OVMF por `scripts/efi-disk-boot-test.sh`. (La vía
squashfs+overlay del [post-etapa-e] queda como optimización futura; el pivote a ext4 ya cumple.)
3. **Contrato de generaciones-grafo** ✅ (módulo `hammer_upgrade::boot_graph` + subcomando `hammer
3. **Contrato de generaciones-grafo** ✅ (módulo `hammer_upgrade::boot_graph` + subcomando `takana
boot`): el modelo in-place de generaciones se lee como un **grafo de estados** navegable (id =
`of_tree`, DAG por `parent`), `hammer boot graph` emite `/run/hammer/boot-graph.json` con el
formato del contrato, y `hammer boot activate <id>` (o `--from-select`) deja el sistema en un nodo
`of_tree`, DAG por `parent`), `takana boot graph` emite `/run/hammer/boot-graph.json` con el
formato del contrato, y `takana boot activate <id>` (o `--from-select`) deja el sistema en un nodo
(no-op si ya vivo · rollback paso a paso a un ancestro · nodo `recovery` sintético · error honesto
ante un "redo" hacia una generación huérfana). Validado e2e por el CLI. **mirada ya puede maquetar
contra el grafo real.**
@@ -129,22 +129,22 @@ muerto antes del takeover).
(`\EFI\BOOT\BOOTX64.EFI`, ruta fallback removible, sin GRUB ni systemd-boot). Como no hay LoadOptions
en esa ruta, el `CONFIG_CMDLINE` horneado (`… initrd=/initramfs.cpio.gz rdinit=/init`) activa el
pivote. Dos caminos, mismos artefactos:
- **host-side** `scripts/install-image-efi.sh` — GPT+ESP+root/store/state, ESP con mtools de hammer,
- **host-side** `scripts/install-image-efi.sh` — GPT+ESP+root/store/state, ESP con mtools de takana,
ext4 con `mke2fs -d` bajo `unshare -r`; **validado VERDE en OVMF** (`scripts/efi-disk-boot-test.sh`:
firmware → BOOTX64.EFI → pivote → arje-zero PID1).
- **live-side** `scripts/hammer-live-install.sh` rama EFI — detecta `/sys/firmware/efi` y hace
**MBR con ESP tipo 0xEF** (UEFI arranca una ESP MBR igual que GPT ⇒ alcanza busybox
fdisk/mkfs.vfat/mount, sin GPT-tool ni mtools en el live). El kernel EFI-stub viaja en el payload
(`iso-image.sh INSTALLER=1` bundlea `bzImage-efi`). **Validación 2-stage VERDE en OVMF**
(`scripts/efi-install-test.sh`): ISO live UEFI → `hammer-install` detecta `/sys/firmware/efi` y toma
(`scripts/efi-install-test.sh`): ISO live UEFI → `takana-install` detecta `/sys/firmware/efi` y toma
la rama EFI → el disco instalado bootea SOLO por EFI-stub soberano (sin GRUB) → arje-zero PID1.
Falta sólo el metal real (quemar USB + bootear, Secure Boot OFF).
## Consecuencias
- La frontera es un **fichero + un comando**, no una API compartida ⇒ los dos agentes avanzan sin
bloquearse. hammer puede emitir el grafo y probar EFI-stub sin esperar a mirada; mirada puede
maquetar el menú contra un `boot-graph.json` de ejemplo sin esperar a hammer.
bloquearse. takana puede emitir el grafo y probar EFI-stub sin esperar a mirada; mirada puede
maquetar el menú contra un `boot-graph.json` de ejemplo sin esperar a takana.
- El menú-grafo es la primera **manifestación del lado proceso** de SDD 15 §H4 — arrancar = activar
un nodo del DAG. No colapsa todo el modelo, pero lo hace visible.
- No mueve la frontera de confianza (ADR 0007/0009): activar un nodo es reproducir/hidratar un árbol
+4 -4
View File
@@ -174,7 +174,7 @@ con `QT_QPA_PLATFORM=offscreen` en el rootfs musl del worker: crea la ventana y
limpio. **El régimen dinámico + zig-cc + musl del ADR queda probado end-to-end en el componente más
difícil de KDE** — el riesgo #1 (¿sobrevive el toolchain a Qt?) está respondido: sí.
**Snapshot `hammer-kde-qtbase-2026-07-11` (img 407438750)**: worker con qtbase + su cierre de deps ya
**Snapshot `takana-kde-qtbase-2026-07-11` (img 407438750)**: worker con qtbase + su cierre de deps ya
sellados. Arrancar futuras `farm-up` de la campaña KDE desde ESTA imagen (no la golden limpia) evita
re-pagar ~45 min de build. El worker efímero se destruyó (baseline €0).
@@ -202,7 +202,7 @@ siguiente de-risk (hito H-QML) — más `qtwayland` como módulo completo (ya te
pero KDE usa `qtwayland` extendido). En paralelo, **saldar la deuda de Capa 0**: sqlite+openssl
shared-PIC y glib `.a` completo, para reactivar SSL/SQL/glib en un qtbase "real" (hoy es el qtbase del
gate, reducido; se rebuildea con features plenas antes de promover a canónico/firmado). Arrancar las
`farm-up` desde el snapshot `hammer-kde-qtbase-2026-07-11`.
`farm-up` desde el snapshot `takana-kde-qtbase-2026-07-11`.
## Resultado H-QML (2026-07-14) — HITO PASADO ✅
@@ -223,7 +223,7 @@ probada, en tres piezas nuevas:
(b) faltaban libs base del cierre. El script trae de `dist/repo` (repo base firmado, cierre
completo) todo paquete ausente (colisión → gana la versión KDE) y agrega entradas ALIAS
qt-corto→qt6-* (mismo `.swm`, otra clave). Índice resultante 884 entradas, re-firmado con
`hammer repo sign``repo verify` trusted.
`takana repo sign``repo verify` trusted.
2. **`scripts/kde/hydrate-fhs.sh`** — hidrata el cierre runtime instalando **cada** paquete del
cierre en el MISMO prefix (clave: `install <pkg> --prefix P` sólo baja los ficheros de `<pkg>`, NO
de su cierre, igual que `product-userland-from-repo.sh`). Con el store del worker caliente los
@@ -247,7 +247,7 @@ probada, en tres piezas nuevas:
`install`-reproduce NO escala al escritorio completo: el hashing determinista es frágil bajo el régimen
dinámico — cada consumidor qt (qtwayland/kwin) recomputa el cierre de build y puede obtener un hash de
qtbase distinto del sellado → **rebuild** (se observó qtbase rebuildeándose al instalar qtwayland). La
vía correcta es **`hammer hydrate <hash> --into` = proyectar el artefacto YA SELLADO del store al FHS,
vía correcta es **`takana hydrate <hash> --into` = proyectar el artefacto YA SELLADO del store al FHS,
sin reproduce**. `scripts/kde/hydrate-from-store.sh`:
- computa el cierre (nombres reales qt6-*) y, por paquete, elige el artefacto `store/<hash>-<name>` MÁS
RECIENTE con mtime **anterior al inicio de hoy** (CUTOFF) = la cosecha FINAL de la granja (set
+13 -13
View File
@@ -1,8 +1,8 @@
# ADR 0013 — Mirror de fuentes: la URL es transporte, el `sha256` es la identidad
- **Estado:** ACEPTADO — implementado en `crates/hammer-build/src/fetch.rs` y `scripts/fuentes/`.
**Ampliado por [ADR 0014](0014-distribucion-multiorigen.md)**: `HAMMER_MIRROR` y
`HAMMER_MIRROR_GIT` son LISTAS de orígenes, no una URL — un solo espejo propio reproducía
**Ampliado por [ADR 0014](0014-distribucion-multiorigen.md)**: `TAKANA_MIRROR` y
`TAKANA_MIRROR_GIT` son LISTAS de orígenes, no una URL — un solo espejo propio reproducía
el punto único de fallo que este documento quería quitar.
- **Fecha:** 2026-08-26
- **Frontera:** `fetch_tarball` / `fetch_git`, `scripts/fuentes/mirror-poblar.sh`,
@@ -84,7 +84,7 @@ métrica que nadie refresca envejece hacia el optimismo.**
Por eso **construir** y **vigilar upstream** son dos trabajos distintos y van en dos sitios distintos:
- `hammer build` **nunca** avisa de que usó el mirror. No es su tarea y volvería el log inútil.
- `takana build` **nunca** avisa de que usó el mirror. No es su tarea y volvería el log inútil.
- `scripts/fuentes/fuentes-vigia.sh` recorre las 1167 fuentes con `curl -I` (cabeceras, sin
descargar) y escribe `docs/state/fuentes-vigia.json`: qué URLs siguen vivas, cuáles dan 404,
cuáles no responden. **Lo corre el latido**, como el grafo de estado.
@@ -97,8 +97,8 @@ Toda descarga **verificada** se promueve al mirror. El mirror se llena solo, con
`scripts/fuentes/mirror-poblar.sh` hace la carga inicial desde `work/tarballs` y sirve para
rellenar lo que falte.
El mirror vive en el Storage Box, bajo `hammer/fuentes/`, direccionado por contenido:
`hammer/fuentes/{sha256}.tar`. Es el mismo Storage Box del respaldo (1 TiB, 938 G libres, ~€3,20/mes
El mirror vive en el Storage Box, bajo `takana/fuentes/`, direccionado por contenido:
`takana/fuentes/{sha256}.tar`. Es el mismo Storage Box del respaldo (1 TiB, 938 G libres, ~€3,20/mes
ya pagados) — no hay infraestructura nueva que mantener.
### 5. ⛔ PROHIBIDO cambiar el `sha256` para «arreglar» una URL muerta
@@ -122,13 +122,13 @@ Ante una URL muerta, en este orden:
## Cómo se activa
```sh
. scripts/fuentes/mirror-env.sh # HAMMER_MIRROR + HAMMER_MIRROR_KEY
flock work/.farm-build.lock ./target/release/hammer --store ./store build <receta>
. scripts/fuentes/mirror-env.sh # TAKANA_MIRROR + TAKANA_MIRROR_KEY
flock work/.farm-build.lock ./target/release/takana --store ./store build <receta>
```
**El mirror es ADITIVO: sin `HAMMER_MIRROR` el comportamiento es exactamente el de siempre.** No se
**El mirror es ADITIVO: sin `TAKANA_MIRROR` el comportamiento es exactamente el de siempre.** No se
hornea un default en el binario a propósito — apuntar por defecto a una máquina concreta convertiría
un fallo de red en un fallo de `hammer` para cualquiera que clone el repo.
un fallo de red en un fallo de `takana` para cualquiera que clone el repo.
⚠️ La ruta sftp lleva `/~/`. curl trata lo que sigue al host en `sftp://` como ruta **absoluta** del
servidor: `…:23/hammer/fuentes` busca en la raíz y da «(78) Could not open remote file for reading»
@@ -143,7 +143,7 @@ descargó un solo byte. La prueba válida es una receta cuyo artefacto NO exista
- receta efímera con el `sha256` de un tarball que **sí** está en el mirror,
- y una URL upstream que **ni resuelve por DNS** (`https://este-host-no-existe.invalid/…`).
Sin `HAMMER_MIRROR`: falla con `curl (6) Could not resolve host`. Con `HAMMER_MIRROR`: **sella**, y el
Sin `TAKANA_MIRROR`: falla con `curl (6) Could not resolve host`. Con `TAKANA_MIRROR`: **sella**, y el
artefacto tiene el contenido real. Los bytes no pudieron venir de ningún otro sitio.
## Consecuencias
@@ -161,7 +161,7 @@ artefacto tiene el contenido real. Los bytes no pudieron venir de ningún otro s
## Fuentes git: bundles shallow por commit
Los 606 repos por commit son el 52% de las fuentes y no los cubre el mirror de tarballs. Se espejan
como **bundles**, en un espacio de nombres propio: `hammer/fuentes-git/{commit}.bundle` (571 commits
como **bundles**, en un espacio de nombres propio: `takana/fuentes-git/{commit}.bundle` (571 commits
distintos — hay commits compartidos entre colas, y se espeja uno solo).
**La identidad es el commit y la verificación la hace git.** Al desempaquetar, git comprueba cada
@@ -172,7 +172,7 @@ que en los tarballs el nombre del fichero ES su verificación.
El bundle se genera desde un `git fetch --depth 1` del commit exacto. Para `act` son **9,3 MB en vez
del repositorio entero**, y con 571 fuentes esa diferencia decide si el mirror cabe. Es legítimo
porque **hammer nunca usa la historia**: lo único que hace con un repo es `git archive <commit> |
porque **takana nunca usa la historia**: lo único que hace con un repo es `git archive <commit> |
tar -x`, o sea materializar un árbol.
### El detalle que costó encontrar: el fichero `shallow`
@@ -232,7 +232,7 @@ suposición sin escribir:
pelar, manda el objeto tag aparte en `refs/tags/hammer-objeto`. Sin ese segundo ref el bundle
desempaqueta bien pero el `cat-file -e <commit>` del otro lado no encuentra lo que la receta pide:
un mirror **correcto pero inútil justo para esas recetas**.
- **hammer** escribía el `commit` de la receta en el fichero `shallow`, que sólo admite SHAs de
- **takana** escribía el `commit` de la receta en el fichero `shallow`, que sólo admite SHAs de
commit. Ya no lo supone: lee la frontera del propio bundle con `git bundle list-heads`, que imprime
la lista de refs **sin necesitar los objetos** y por eso sirve justo antes del fetch. Y trae
`refs/*:refs/*` en vez de un ref concreto, para que el objeto tag viaje con lo demás.
+5 -5
View File
@@ -20,8 +20,8 @@ apoyamos exactamente en eso, en tres capas a la vez:
| capa | qué sirve | orígenes soportados **antes** de este ADR |
|---|---|---|
| fuentes tarball | `{sha256}.tar` | **1** (`HAMMER_MIRROR`) |
| fuentes git | `{commit}.bundle` | **1** (`HAMMER_MIRROR_GIT`) |
| fuentes tarball | `{sha256}.tar` | **1** (`TAKANA_MIRROR`) |
| fuentes git | `{commit}.bundle` | **1** (`TAKANA_MIRROR_GIT`) |
| repo de paquetes | `index.json` + `.swm` | **1** (`--repo <URL>`) |
El ADR 0013 existe precisamente para no depender de 79 servidores ajenos — y lo resolvió creando una
@@ -56,7 +56,7 @@ espejos» de un problema de infraestructura y de contratos a un problema de a qu
### 2. Todo camino de descarga acepta una LISTA de orígenes, no uno
`HAMMER_MIRROR` y `HAMMER_MIRROR_GIT` pasan a ser listas separadas por comas. `--repo` acepta
`TAKANA_MIRROR` y `TAKANA_MIRROR_GIT` pasan a ser listas separadas por comas. `--repo` acepta
`https://a,https://b`. Añadir orígenes **no re-hashea nada**: la URL nunca estuvo en `hash_inputs`
(ADR 0013 §1), y para el repo el `digest` no es un input de build sino un campo del índice.
@@ -109,11 +109,11 @@ escribir un byte. La cadena queda: **clave raíz → firma del índice → `dige
> `b3:<hex>` **deje de ser el hash que obtiene un tercero** con `b3sum` sobre el mismo archivo.
>
> Lo destapó escribir la contraparte de este ADR para churay (`02_ruway/churay/SDD-DISTRIBUCION.md`
> §5 en tawasuyu): churay direcciona sus blobs con `blake3(bytes)` pelado y hammer iba a hacerlo
> §5 en tawasuyu): churay direcciona sus blobs con `blake3(bytes)` pelado y takana iba a hacerlo
> length-prefijado, **bajo el mismo prefijo `b3:`** ⇒ el mismo archivo con dos hex distintos, y un
> CAS compartido que no falla ruidosamente sino que busca nombres distintos para los mismos bytes.
>
> Ahora usa `ArtifactHash::of_bytes` — que **ya existía en hammer** y ya era blake3 pelado; es la
> Ahora usa `ArtifactHash::of_bytes` — que **ya existía en takana** y ya era blake3 pelado; es la
> convención de `of_file` y la del `expected_hash` de un `.swm`, así que `of_inputs` era además la
> pieza fuera de sitio *dentro* del propio repo. Coste del cambio: una línea, porque ningún índice
> publicado llevaba todavía el campo. Es el argumento de este ADR aplicado a sí mismo — decidir
+20 -20
View File
@@ -3,13 +3,13 @@
- **Estado:** PROPUESTO — nombre ADOPTADO (`qorpa`, 2026-09-03); implementación EN CURSO por el
§Orden de trabajo. Este documento decide la frontera; el código viene después.
- **Fecha:** 2026-09-03
- **Frontera (a crear):** `hammer qorpa {pull,create,run,export,list,prune}`,
- **Frontera (a crear):** `takana qorpa {pull,create,run,export,list,prune}`,
`/var/lib/hammer/qorpa/images/<sha256>/`, `/var/lib/hammer/qorpa/instances/<id>/`,
`instance.toml` (manifiesto), clase de nodo `ajeno` en `build-state.py`.
- **Superficie:** verbos en inglés, mensajes en castellano — `CLAUDE.md` regla 4. Este ADR nació
proponiendo `traer/crear/correr` y se corrigió el 2026-09-03; el nombre `qorpa` sí es quechua.
- **Continúa:** [SDD 04](../04-overlay.md) (overlay), [SDD 16](../16-harkaq-jaula.md) (harkaq),
[ADR 0004](0004-no-custom-nix.md) (hammer no usa nix), [SDD 20](../20-catalogo-publicable-y-completa.md)
[ADR 0004](0004-no-custom-nix.md) (takana no usa nix), [SDD 20](../20-catalogo-publicable-y-completa.md)
(catálogo publicable).
- **Absorbe:** [`plan-jaula-juegos.md`](../plan-jaula-juegos.md) §Capa 3. Su **F1** pasa a ser un
caso particular de este ADR (imagen de tipo 2); su **F0** (flatpak al catálogo) queda innecesaria;
@@ -62,7 +62,7 @@ Este ADR compone piezas existentes. No inventa infraestructura:
| pieza | dónde | estado |
|---|---|---|
| aislamiento de namespaces | `recipes/bwrap.toml` | sellada; ya es dep del lab de build |
| capa mutable sobre base inmutable | [SDD 04](../04-overlay.md) + `crates/hammer-overlay` | overlayfs, `hammer try` |
| capa mutable sobre base inmutable | [SDD 04](../04-overlay.md) + `crates/hammer-overlay` | overlayfs, `takana try` |
| política de grano fino en el kernel | [SDD 16](../16-harkaq-jaula.md) | Landlock + seccomp; fase 1 ✅, barrido con 0 irreducibles |
| rootfs ajeno pineado por sha256 | `scripts/lab-image.sh` | en producción desde 2026-08-10 |
| `newuidmap` / `newgidmap` / `/etc/subuid` | `recipes/shadow.toml` | **instalados**; falta provisionarlos |
@@ -127,7 +127,7 @@ host «para ahorrar espacio».
> **Una instancia se declara, no se acumula.**
Si el `upper` con 200 paquetes instalados a mano fuese el activo, tendríamos un blob irreemplazable:
justo lo que hammer existe para no tener. La relación correcta es la misma que receta↔artefacto:
justo lo que takana existe para no tener. La relación correcta es la misma que receta↔artefacto:
Los campos van en inglés como el resto de la superficie (`CLAUDE.md` regla 4):
@@ -149,7 +149,7 @@ apps = ["steam.desktop"]
```
Consecuencias que se caen solas:
- **`hammer qorpa recreate <id>`** reconstruye la instancia desde el manifiesto. El `upper` es
- **`takana qorpa recreate <id>`** reconstruye la instancia desde el manifiesto. El `upper` es
descartable.
- **Actualizar la base** (Fedora 43 → 44) no es un rebase riesgoso: se cambia el digest y se recrea.
- **El respaldo** es el manifiesto (KB), no el `upper` (GB). `respaldo-storagebox.sh` no toca
@@ -159,7 +159,7 @@ Consecuencias que se caen solas:
**✅ COMPLETADO 2026-09-03 — hasta acá `packages` era una lista que nadie leía.** Durante unas
horas el ADR afirmaba las cuatro consecuencias de arriba mientras `recreate` confesaba en su propia
salida que «instalarlos todavía es a mano»: o sea que el `upper` **sí** era el activo y el
manifiesto un adorno. Ahora `hammer qorpa provision <id>` instala lo declarado, y **`recreate` lo
manifiesto un adorno. Ahora `takana qorpa provision <id>` instala lo declarado, y **`recreate` lo
llama solo** (`--no-provision` para no hacerlo).
| decisión | por qué |
@@ -193,8 +193,8 @@ se sella y su digest entra en el índice firmado. El tipo 1 existe porque `dnf i
precisamente lo que el tipo 2 no permite.
**⚠ Corrección de vocabulario (2026-09-04).** Este ADR decía «entra al store por `file_drop`», y
`file_drop` en hammer es **otra cosa**: una operación de `hammer apply` que coloca un fichero en el
sistema instalado verificando su hash (`hammer-core::apply`). No tenía nada que ver con sellar. Lo
`file_drop` en takana es **otra cosa**: una operación de `takana apply` que coloca un fichero en el
sistema instalado verificando su hash (`takana-core::apply`). No tenía nada que ver con sellar. Lo
que sella de verdad es lo de siempre —**una receta**—, con una marca nueva: `foreign = true`. Se
deja escrito porque un término inventado que suena a mecanismo existente es peor que un hueco: manda
a buscar el código donde no está.
@@ -206,7 +206,7 @@ El objetivo es el poder de Bedrock Linux —un espacio de nombres unificado, app
`exec`, integración a nivel de PID 1, y arbitraje por heurística de qué binario gana.
Acá no hace falta nada de eso, porque **el FHS de esta distro ya es una proyección, no la verdad**:
`hammer hydrate` proyecta artefactos del store a un árbol. Extender la proyección a instancias es
`takana hydrate` proyecta artefactos del store a un árbol. Extender la proyección a instancias es
idiomático. Tres capas:
**Capa 1 — shims.** Al crear o actualizar una instancia se **generan** lanzadores finos en el
@@ -289,7 +289,7 @@ Dos preguntas que se confunden y tienen respuestas distintas.
| qué | ¿pineado? | cómo se actualiza |
|---|---|---|
| el rootfs base | **sí**, por sha256 | cambiar el digest en `instance.toml` + `hammer qorpa recreate` |
| el rootfs base | **sí**, por sha256 | cambiar el digest en `instance.toml` + `takana qorpa recreate` |
| lo que instalás adentro (`dnf install steam`, `pacman -Syu`) | **no, y no puede estarlo** | con el gestor de la imagen, cuando quieras |
Subir la base de versión es barato **precisamente por D3**: como el manifiesto es la verdad y el
@@ -300,7 +300,7 @@ El riesgo real del pin —que upstream borre el tarball— **ya está resuelto p
[ADR 0013](0013-mirror-de-fuentes.md)**: la URL no entra en la identidad, sólo el sha256, así que
espejar una imagen es gratis y una imagen pineada no se puede perder.
**b) Pinear ≠ lista cerrada.** `hammer qorpa pull <url> --sha256` acepta cualquier rootfs. Lo corto
**b) Pinear ≠ lista cerrada.** `takana qorpa pull <url> --sha256` acepta cualquier rootfs. Lo corto
no es lo que *se puede* traer, sino lo que **nosotros probamos, espejamos y publicamos**, porque cada
imagen curada es una segunda cadena de suministro que hay que sostener (§NO-resuelve 3). Se curan
**tres**, y cada una entra por un trabajo distinto — no por sabor:
@@ -334,7 +334,7 @@ se hashea, porque no hay receta y un hash afirmaría que lo reproducimos. El sel
hash**: está en el store, y que un artefacto exista mientras el grafo lo niega sería otra forma de
mentir. Lo que comparten es el estado —`ajeno`, fuera del corpus—, que es lo que protege la cifra.
**Se consume con `hammer qorpa import --from-store <hash>`**, y ahí está el detalle que hace que
**Se consume con `takana qorpa import --from-store <hash>`**, y ahí está el detalle que hace que
todo esto valga: la imagen se registra bajo el **sha256 del archivo de upstream** que el artefacto
declara, **no** bajo su `ArtifactHash`. Si se registrara por `ArtifactHash`, la imagen importada del
store y la traída con `pull` serían dos imágenes distintas con los mismos bytes y las instancias de
@@ -381,10 +381,10 @@ builds:
|---|---|
| **seccomp** | bwrap **no instala filtro alguno**: hoy una instancia puede `io_uring`, `bpf`, `ptrace`, `process_vm_readv`, `userfaultfd`, `keyctl`, `perf_event_open`, `init_module`, `kexec`. Ésa es superficie de kernel contra un prebuilt de terceros |
| **`no_new_privs`** | ningún setuid de la imagen ajena escala adentro |
| **canal de evidencia** | con Landlock ABI ≥ 7 el kernel audita las denegaciones ⇒ se puede saber **qué intentó tocar** un binario cerrado. Es la única forma de auditar el montón B. ✅ **cableado 2026-09-03**: `hammer qorpa run <id> --evidence` |
| **canal de evidencia** | con Landlock ABI ≥ 7 el kernel audita las denegaciones ⇒ se puede saber **qué intentó tocar** un binario cerrado. Es la única forma de auditar el montón B. ✅ **cableado 2026-09-03**: `takana qorpa run <id> --evidence` |
| **`seal_image`** | lo único del eje fs que el namespace no da: congelar `/usr`, `/bin`, `/lib`, `/opt` **aunque adentro seas root**, para que un exploit no persista en el `upper` |
**El canal de evidencia, cableado (2026-09-03).** `hammer qorpa run <id> --evidence` levanta el
**El canal de evidencia, cableado (2026-09-03).** `takana qorpa run <id> --evidence` levanta el
lector (`harkaq-audit`) en el HOST —el audit no está namespeceado y los registros cruzan— antes de
que arranque la instancia, y al terminar imprime uno de **tres** estados. Los tres se verificaron a
mano y quedan como guardián en `scripts/qorpa/evidence-probe.sh`:
@@ -511,7 +511,7 @@ Se escriben acá para que no se descubran en producción.
(no podía bajar a `_apt`), `pacman` (no podía chownear su descarga a `alpm`) y el userns anidado
de pressure-vessel. **Tres síntomas, una causa.**
La cura entera —`hammer qorpa run` la aplica sola cuando puede— tiene **tres partes, y ninguna
La cura entera —`takana qorpa run` la aplica sola cuando puede— tiene **tres partes, y ninguna
sobra**:
| pieza | por qué |
@@ -531,13 +531,13 @@ Se escriben acá para que no se descubran en producción.
`recreate` y la poda tienen que entrar a un userns mapeado para limpiar
(`unshare -U --map-auto -r rm -rf`). Quitar el impuesto mueve el problema, no lo evapora.
3. **Segunda cadena de suministro, sin ninguna garantía de hammer.** Sin repro, sin cierre firmado,
3. **Segunda cadena de suministro, sin ninguna garantía de takana.** Sin repro, sin cierre firmado,
sin escaneo de licencias. El riesgo no es técnico: es que se normalice. Mitigación = D5 capa 3
(clase `ajeno` en el grafo) + un renglón explícito en [SDD 20](../20-catalogo-publicable-y-completa.md):
**las imágenes ajenas no entran en el catálogo publicable ni en el reporte de licencias, porque
no podemos enumerarlas.**
4. **El alcance del claim.** Desde el día que exista una instancia, la frase «hammer reproduce bit a
4. **El alcance del claim.** Desde el día que exista una instancia, la frase «takana reproduce bit a
bit» hay que acotarla por escrito: *el sistema base reproduce; las instancias ajenas no, y se
declaran como tales*. Sin eso, la cultura de números honestos se erosiona sola — que es
exactamente la falla que ya nos pasó con el store-gc y con la deuda fantasma.
@@ -583,7 +583,7 @@ Se escriben acá para que no se descubran en producción.
1. **Provisionar subuid** y probar `dnf install` en una instancia mínima. Es lo primero que falla
(§NO-resuelve 2) y define si el resto es fácil o difícil.
2. ✅ **HECHO 2026-09-03.** `hammer qorpa pull <url> --sha256` + verificación de digest antes de
2. ✅ **HECHO 2026-09-03.** `takana qorpa pull <url> --sha256` + verificación de digest antes de
desempacar. El rootfs se ancla por estructura (`etc/` + `usr|bin`), con `--subdir` de escape.
3. ✅ **HECHO 2026-09-03.** Instancia = overlay sobre la imagen + `instance.toml`; `create` /
`recreate` / `run`. El overlay lo monta bwrap (`--overlay-src` + `--overlay`) dentro de su
@@ -644,7 +644,7 @@ Se escriben acá para que no se descubran en producción.
**Lo que sigue faltando, y ahora es sólo esto:** un juego real dibujando, que pide sesión
gráfica y credenciales. Ninguna de las dos es una pregunta de diseño.
7. ✅ **HECHO 2026-09-03.** `hammer qorpa prune` + el renglón en
7. ✅ **HECHO 2026-09-03.** `takana qorpa prune` + el renglón en
[SDD 20](../20-catalogo-publicable-y-completa.md#imágenes-ajenas-qorpa-fuera-del-catálogo-y-por-escrito).
Poda restos de pulls a medias, imágenes que ninguna instancia usa (se re-traen por digest: es la
propiedad que da el pin) y, con `--upper`, las capas mutables. **Sin `--yes` es un simulacro**, y
@@ -659,7 +659,7 @@ El subsistema se llama **`qorpa`** — *huésped alojado*: alguien que entra a l
habitaciones concretas y no las demás. Elegido por el autor del repo; entra en la familia (harkaq,
yupana, arje, wawafs, kikin, churay, mirada, minga).
Consecuencias de nombre, ya aplicadas arriba: la frontera es `hammer qorpa {…}`, el espacio de
Consecuencias de nombre, ya aplicadas arriba: la frontera es `takana qorpa {…}`, el espacio de
nombres es `/var/lib/hammer/qorpa/{imagenes,instancias}/` —uno solo, para que la poda de
§NO-resuelve 5 tenga un único árbol que barrer— y el adjetivo del grafo sigue siendo `ajeno`
(clase de nodo), porque describe la **procedencia** del nodo, no el subsistema que lo aloja.
+4
View File
@@ -1,6 +1,10 @@
# ADR 0016 — Renombre del sistema: `hammer``takana`
- **Estado:** ACEPTADO (decisión del usuario, 2026-09-09)
- ⚠ **Este fichero queda FUERA de todo barrido de renombre.** Habla *sobre* el cambio de nombre, así
que necesita seguir diciendo `hammer` donde corresponde. El barrido de la etapa 5 lo pasó por
arriba y lo dejó diciendo «Renombre del sistema: takana → takana»; se revirtió. Si alguien vuelve
a barrer docs, excluir este fichero explícitamente.
- **Reemplaza:** nada. **Afecta:** toda la superficie de CLI (regla 4 de `CLAUDE.md`).
## Contexto
+12 -12
View File
@@ -1,7 +1,7 @@
# Joyas reusables — trucos de otras distros que encajan en piezas hammer existentes
# Joyas reusables — trucos de otras distros que encajan en piezas takana existentes
**Fecha:** 2026-07-16 · **Criterio de entrada:** o está probado por alguien serio, o el CAS de
hammer lo vuelve trivial donde a otros les cuesta. Cada joya nombra la pieza hammer que la
takana lo vuelve trivial donde a otros les cuesta. Cada joya nombra la pieza takana que la
recibe — sin pieza receptora, no entra. Complementa `plan-freebsd-aprovechables.md`,
`plan-variantes-cpu.md` y `plan-jaula-juegos.md`.
@@ -11,20 +11,20 @@ recibe — sin pieza receptora, no entra. Complementa `plan-freebsd-aprovechable
**Qué es.** overlayfs+EROFS montando un árbol desde un **object store content-addressed**, con
fs-verity por objeto. Probado en producción: ostree/bootc/Fedora Atomic lo usan hoy. Está
diseñado *literalmente* para stores como el de hammer.
diseñado *literalmente* para stores como el de takana.
**Dónde encaja.** Backend alternativo a la hidratación por hardlink (ADR 0005): en vez de
materializar N hardlinks, un manifiesto (imagen EROFS chiquita, firmable) monta el árbol
directo del store. Y se lleva puesto medio **cierre §5** (verity + medida): el hash que hammer
directo del store. Y se lleva puesto medio **cierre §5** (verity + medida): el hash que takana
ya usa como identidad se convierte en verificación por-lectura del kernel. Coste kernel: EROFS
+ OVERLAY_FS + FS_VERITY, tres líneas más del contrato `scripts/config`.
**Gate barato:** prototipo de `hammer hydrate --backend composefs` sobre un artefacto sellado;
**Gate barato:** prototipo de `takana hydrate --backend composefs` sobre un artefacto sellado;
comparar con hardlink en frío/caliente. No reemplaza ADR 0005 — convive como backend.
**Prototipo MEDIDO (2026-07-17, laptop, composefs 1.0.8, sobre el product-rootfs sellado
`ba351f1b…`, 262MB / 730 entradas):**
- `hammer hydrate` (hardlink, ADR 0005): **28ms**. `mkcomposefs --digest-store`: **507ms**
- `takana hydrate` (hardlink, ADR 0005): **28ms**. `mkcomposefs --digest-store`: **507ms**
(incluye hashear todo al objects/). El manifiesto `.cfs` resultante: **136KB firmables**
para el rootfs entero.
- La comparación de velocidad es la métrica equivocada: ambos son sub-segundo. Lo que
@@ -36,7 +36,7 @@ comparar con hardlink en frío/caliente. No reemplaza ADR 0005 — convive como
- **Hallazgo de diseño**: el objects/ de composefs se indexa por digest **fs-verity
(sha256)**, no por `b3:` ⇒ el backend mantiene un objects/ paralelo al store (lo
construye `mkcomposefs --digest-store` solo, y DEDUPLICA contenido entre artefactos)
o hammer guarda el mapa b3→verity al sellar. No es bloqueante, es una tabla.
o takana guarda el mapa b3→verity al sellar. No es bloqueante, es una tabla.
- **Pendiente (pide root)**: montar el `.cfs` (overlayfs+EROFS) y medir lectura
fría/caliente vs hardlink. El kernel linux-metal re-sellado ya trae EROFS+FS_VERITY.
@@ -46,7 +46,7 @@ comparar con hardlink en frío/caliente. No reemplaza ADR 0005 — convive como
pero rompen la bit-repro de las distros: el perfil varía corrida a corrida, así que
reproducible-builds y PGO viven peleados.
**El truco hammer.** El perfil se **sella como artefacto**: se genera una vez con carga
**El truco takana.** El perfil se **sella como artefacto**: se genera una vez con carga
representativa, entra al store por hash, y la receta lo declara como input. El build vuelve a
ser función pura `(fuente, perfil) → binario` — PGO bit-reproducible, verificable por la
granja como cualquier receta. Nadie tiene esto porque nadie trata los perfiles como inputs
@@ -58,12 +58,12 @@ más barata — es la única optimización que se cobra en cada build futuro, no
## 3. mimalloc en estático — el talón de musl
**El problema.** El allocator de musl (mallocng) es el punto débil de rendimiento conocido de
toda distro musl, sobre todo multihilo. Y como hammer linkea estático, cada binario *hornea* su
toda distro musl, sobre todo multihilo. Y como takana linkea estático, cada binario *hornea* su
allocator — no hay LD_PRELOAD que valga después.
**El truco (probado: Chimera Linux lo envía system-wide).** Linkear mimalloc en las
herramientas calientes, per-receta como el patrón `.hammer-zig-ar` (NO framework: re-hashearía
los sellados). Candidatas: las propias herramientas hammer y todo lo alloc-intensivo de la
los sellados). Candidatas: las propias herramientas takana y todo lo alloc-intensivo de la
lista caliente de `plan-variantes-cpu.md` — es la otra mitad de esa moneda: v3 acelera el
cómputo, mimalloc el malloc.
@@ -77,7 +77,7 @@ T1.x) — mismo re-sellado, cero código.
## 5. uutils coreutils — de-Alpinización que compila en el LAPTOP
**Probado:** Ubuntu 25.10 los envía como default. Son los coreutils en Rust — y ahí está el
encaje hammer: **Rust compila en el laptop** (la regla "recetas C ⇒ worker" no aplica). Cada
encaje takana: **Rust compila en el laptop** (la regla "recetas C ⇒ worker" no aplica). Cada
utilidad de la clase busybox/coreutils que hoy es deuda declarable puede migrar a una receta
Rust sin tocar la cola del worker. No es rendimiento: es soberanía de cadena con el compilador
que ya es el nativo de la casa (ADR 0001).
@@ -101,7 +101,7 @@ que las aplica es trivial y no hace falta traer `ananicy-cpp` entero.
## Lo que se miró y NO entra
- **DT_RELR / prelink** — ganancias de arranque para *dinámico*; con link=static es ruido.
- **ccache/sccache** — la memoización de hammer es por contenido y sellada (H2); un cache
- **ccache/sccache** — la memoización de takana es por contenido y sellada (H2); un cache
mutable por-builder la contradice.
- **ksm en la granja** — dedup de páginas entre VMs efímeras que viven minutos: no amortiza.
+1 -1
View File
@@ -397,7 +397,7 @@ mpfr LGPL-3.0-or-later
lua MIT
# ── Código PROPIO (fuente en git.tawasuyu.net) — MIT ───────────────────────────────────────────
# Decisión del usuario, 2026-08-07: «tawasuyu está en MIT». Es la misma licencia que hammer
# Decisión del usuario, 2026-08-07: «tawasuyu está en MIT». Es la misma licencia que takana
# (LICENSE en la raíz + Cargo.toml), así que todo lo nuestro queda coherente bajo un solo término.
#
# ⚠ OJO con `libelogind`: por el nombre parece el elogind de upstream (LGPL-2.1+) y NO lo es —
1 # Tabla CURADA de licencias, para `scripts/licencias.sh --sembrar`. SDD 19 §2.1.
397 arje-zero-attest
398 cosmos-cli
399 dominium-cli
400 libelogind
401 llimphi-counter
402 mirada-compositor
403 mirada-ctl
+3 -3
View File
@@ -17,8 +17,8 @@ giflib MIT COPYING (texto de MIT)
glab MIT LICENSE (texto de MIT)
go BSD-3-Clause LICENSE (texto de BSD-3-Clause)
gtk4-hello LGPL-2.1-or-later meson.build: license: 'LGPL-2.1-or-later'
hammer-edit LGPL-2.1-or-later meson.build: license: 'LGPL-2.1-or-later'
hammerd MIT el repo es el propio hammer: LICENSE (MIT License) + Cargo.toml: license = "MIT"
takana-edit LGPL-2.1-or-later meson.build: license: 'LGPL-2.1-or-later'
hammerd MIT el repo es el propio takana: LICENSE (MIT License) + Cargo.toml: license = "MIT"
intltool GPL-2.0-or-later COPYING + concesión en intltool-extract.in: …on; either version 2 of the # License, or (at your option) any later version. # # Intltool is d…
json-c MIT COPYING (texto de MIT)
libogg BSD-3-Clause COPYING (texto de BSD-3-Clause)
@@ -36,7 +36,7 @@ nano GPL-3.0-or-later COPYING + concesión en NEWS: …Finally, nano is now lice
nghttp2 MIT COPYING (texto de MIT)
pcre2-shared BSD-3-Clause LICENCE.md (texto de BSD-3-Clause)
perl-xml-parser Artistic-2.0 LICENSE es el texto íntegro de "The Artistic License 2.0" (8937 bytes, Copyright 1998-2000 Larry Wall y Clark Cooper)
portal-probe MIT el repo es el propio hammer: LICENSE (MIT License) + Cargo.toml: license = "MIT"
portal-probe MIT el repo es el propio takana: LICENSE (MIT License) + Cargo.toml: license = "MIT"
scdoc MIT COPYING (texto de MIT)
seatd MIT meson.build: license: 'MIT'
socat GPL-2.0-or-later COPYING + concesión en socat_buildscript_for_android.sh: …; either version 2.1 of the License, or (at your option) any later version. The GNU C Libr…
Can't render this file because it contains an unexpected character in line 13 and column 41.
+1 -1
View File
@@ -22,7 +22,7 @@ edita la cmdline en vivo (para alternar software/hardware, ver abajo).
`libxkbcommon`, datos XKB, PAM permisivo, busybox, loader musl.
- `/sbin/init` = lanzador: monta pseudo-fs, arranca `seatd`, corre `mirada-compositor --drm --greeter`.
La receta nueva es `recipes/mesa-swrast.toml` (softpipe hammer-nativo, sin LLVM); el stack robusto de la
La receta nueva es `recipes/mesa-swrast.toml` (softpipe takana-nativo, sin LLVM); el stack robusto de la
imagen viene de Alpine (deuda de soberanía: falta `recipes/{llvm,mesa-llvmpipe,nvk}` desde fuente).
## Estado validado (QEMU virtio-gpu, software)
+6 -6
View File
@@ -67,7 +67,7 @@ está rota».
Un solo fichero declarativo. Un `perfil` = una imagen enviable.
```toml
schema = "hammer-targets/1"
schema = "takana-targets/1"
[perfil.base]
descripcion = "userland foundational: la distro arranca y se usa"
@@ -121,7 +121,7 @@ Salida a un fichero **aparte**, `docs/state/seed-edges.json`, con procedencia po
(`{dep, fuente: "nix"|"alpine", fecha}`). Nunca a `targets.toml`, nunca a `recipes/`.
**Disciplina, en negrita porque es donde esto se puede pudrir: la semilla es una HIPÓTESIS, no la
verdad.** Los nombres son de upstream (`pkg-config` no existe en hammer, que usa zig-cc; `stdenv` y
verdad.** Los nombres son de upstream (`pkg-config` no existe en takana, que usa zig-cc; `stdenv` y
los hooks de autoreconf no son nada acá). Hace falta una tabla de mapeo de nombres y una lista de
descarte de nix-ismos. Va a haber ruido. Su único trabajo es hacer la frontera **contable y
ordenable**, no correcta. Nota de ADR: leer la *metadata* de nixpkgs no viola el ADR 0004 — lo que
@@ -142,7 +142,7 @@ intuición. Muestra final: 60 recetas, 686 deps declaradas.
información **sí está** (84%), sólo hace falta aplanarla.
**Decisión: se siembran aristas DIRECTAS; la transitividad la hace el grafo.** Las dos distros ponen
la frontera en lugares distintos — hammer APLANA la clausura en `[deps].build` (cada dep es una capa
la frontera en lugares distintos — takana APLANA la clausura en `[deps].build` (cada dep es una capa
`--overlay-src` del sandbox: si no está declarada, no está en el árbol), nixpkgs declara sólo las
directas y propaga. Reproducir el aplanado con el cierre transitivo de nixpkgs arrastra su universo
de bootstrap: **4629 nodos fantasma** (`glibc-locales`, `autoconf`, `texinfo`, `help2man`). Como
@@ -152,7 +152,7 @@ que además envenena el grafo.
Lo que la calibración enseñó, y que ninguna intuición habría dado:
- **Cuatro clases de discrepancia, no una.** No todo desacuerdo es ruido: `make`/`binutils`/
`linux-headers` son **convención de hammer** (nix las da por implícitas en el stdenv); `zlib` vs
`linux-headers` son **convención de takana** (nix las da por implícitas en el stdenv); `zlib` vs
`zlib-shared` es una **decisión nuestra** de enlazado que nix no puede conocer; `cargo`/`rustc` los
**provee el lab**, no una receta (las recetas Rust declaran `deps = []`); y recién lo que queda es
ruido de verdad. Contarlas juntas hacía parecer irreducible lo que era clasificable.
@@ -166,14 +166,14 @@ Lo que la calibración enseñó, y que ninguna intuición habría dado:
**Y un hallazgo de secuencia: hoy hay 0 nodos `wanted`**, porque `targets.toml` se pobló por
lift-and-shift de lo que ya existe. Un sembrador de nodos inexistentes no tiene a quién sembrar. De
ahí el modo que sí da valor ya: `--frontera <perfil>` siembra las recetas que YA existen en la
clausura de un perfil y reporta lo que nixpkgs pide y hammer no tiene receta para construir —
clausura de un perfil y reporta lo que nixpkgs pide y takana no tiene receta para construir —
descubre la frontera desde el corpus actual en vez de esperar a que alguien la declare. Sale a
`docs/state/seed-frontera.json`, versionado, para que el `git diff` muestre qué huecos aparecen.
Primer barrido: **137 candidatos en `escritorio-kde`** (`kdoctools` ×6, `milou`, `polkit-qt-1`,
`libkscreen`, `qqc2-breeze-style`, y `mesa-libgbm`/`libglvnd`/`spirv-tools` — que son exactamente el
muro de GBM/EGL ya documentado), **51 en `cli`**, 46 en `escritorio-mirada`.
Cada candidato es una **hipótesis a clasificar por un humano**: (a) dep opcional que hammer no
Cada candidato es una **hipótesis a clasificar por un humano**: (a) dep opcional que takana no
habilitó a propósito, (b) nix-ismo que falta filtrar, o (c) hueco real. El sembrador no decide cuál;
los ordena por cuántas recetas los piden. **Nada se promueve a receta automáticamente.**
+9 -9
View File
@@ -1,7 +1,7 @@
# Plan — qué tomar de FreeBSD
**Fecha:** 2026-07-16 · **Origen:** análisis de META MODE/filemon, Capsicum/casper y
libarchive contra el estado real de hammer (harkaq Fase 2, cierres §2/§3, catálogo Etapa G).
libarchive contra el estado real de takana (harkaq Fase 2, cierres §2/§3, catálogo Etapa G).
**Regla del análisis:** de otro kernel se toma *diseño*, no código. La única pieza de código
importable es libarchive (como receta del catálogo, igual que cualquier otra). Todo lo demás
@@ -17,7 +17,7 @@ normalizado. Con eso hacen auto-descubrimiento de dependencias y detección de b
si la traza cambia, el target se reconstruye. Llevan ~12 años iterando qué normalizar (orden,
tmpdirs, accesos que no importan).
**Qué tiene hammer hoy.** harkaq mide sólo evidencia **negativa**: denegaciones Landlock bajo
**Qué tiene takana hoy.** harkaq mide sólo evidencia **negativa**: denegaciones Landlock bajo
política=clausura (`harkaq-audit.c` emite JSON `{path, motivo}`, `harkaq-verdict.py` clasifica
esperada/deuda). Lo que NO mide: **qué parte de la clausura concedida se usó de verdad**.
@@ -59,7 +59,7 @@ contrato que suggest). Piloto: 3 recetas ya instrumentadas (zlib, tar, brotli),
`--tmp-overlay` del sandbox: el superbloque del overlay es OTRO, y overlayfs abre los
lower por dentro sin fsnotify en el sb de abajo. Medido: build completo de zlib-ng con
traza sobre el store = **0 eventos**; los binds sí aparecen (206 eventos de `/src` vía
`work/sources`, mismo sb) e incluso los reads host-side de hammer (`recipes/*.toml` al
`work/sources`, mismo sb) e incluso los reads host-side de takana (`recipes/*.toml` al
resolver deps — la traza ya sirve para auditar el hub).
2. **La salida, validada**: marcar `--fs /proc/<pid-bwrap>/root` — el magic-link cruza al
mount-ns y la marca cae sobre el SB DEL OVERLAY. Test sintético en el worker (overlay en
@@ -68,12 +68,12 @@ contrato que suggest). Piloto: 3 recetas ya instrumentadas (zlib, tar, brotli),
el mismo idioma de la política harkaq, sin traducción host↔jaula. Cero código nuevo:
`harkaq-trace --fs /proc/$PID/root --prefix /usr`.
3. ~~Pendiente de orquestación~~ **HECHO en forma piloto**: `harkaq-trace-build.sh` envuelve
un `hammer build`, engancha un tracer a cada bwrap que aparece (un bwrap por fase = una
un `takana build`, engancha un tracer a cada bwrap que aparece (un bwrap por fase = una
traza por fase, la granularidad del veredicto) y emite canónica + sello + tabla
declaradas-vs-usadas. La integración sin carrera (lanzar el tracer ANTES del exec, no
por polling) sigue siendo del harness — coordinar con Opus.
**PILOTO END-TO-END (2026-07-17, worker, `hammer build` real de zlib en store desechable
**PILOTO END-TO-END (2026-07-17, worker, `takana build` real de zlib en store desechable
con deps hardlinkeadas — cero contaminación):**
- **El build trazado SELLÓ y el hash REPRODUJO bit a bit un artefacto ya sellado del store
@@ -102,7 +102,7 @@ con deps hardlinkeadas — cero contaminación):**
hay una clase de necesidades — DNS, certificados TLS, syslog, random temprano, locale — que
son *servicios del mundo*, no paths, y no caben en ninguna jaula por-fichero.
**Dónde muerde en hammer.** En build: en ninguna parte — los builds son offline por
**Dónde muerde en takana.** En build: en ninguna parte — los builds son offline por
`--unshare-all`, la clase casper es vacía por construcción. En **runtime** (cierre §3: derivar
la política Landlock del binario instalado desde la clausura medida en build): la clausura de
build **no contiene** esa clase ⇒ derivar a ciegas produce binarios que revientan en el primer
@@ -114,7 +114,7 @@ build **no contiene** esa clase ⇒ derivar a ciegas produce binarios que revien
de build **+ clases de servicio declaradas** (`dns`, `tls-certs`, `random`, `locale`,
`syslog`). Se declara por clase con nombre, nunca se ensancha en silencio — D3 aplica en
runtime igual que en build.
- **T2.2 — mapa clase→paths para musl.** Ventaja estructural de hammer: musl no tiene
- **T2.2 — mapa clase→paths para musl.** Ventaja estructural de takana: musl no tiene
nsswitch ni dlopen de módulos NSS — `dns` = `/etc/resolv.conf` + socket UDP; `tls-certs` =
`/etc/ssl/certs`; `random` = `getrandom(2)` (ni path necesita). El mapa completo cabe en
media página; en glibc esto era intratable y es la mitad de por qué casper existe.
@@ -131,7 +131,7 @@ El único export de código real de FreeBSD que todo el mundo usa.
- **T3.1 — `recipes/libarchive.toml`.** Receta C ⇒ worker. Nombre nuevo en el catálogo, sin
riesgo de colisión homónima (el gotcha de promote no aplica). Deps mínimas: zlib (xz/zstd
opcionales según features que se activen).
- **T3.2 — verificación cruzada de formato.** Donde hammer serializa árboles (pack/export),
- **T3.2 — verificación cruzada de formato.** Donde takana serializa árboles (pack/export),
extraer con bsdtar y comparar el árbol resultante contra el extractor propio = **consenso de
implementación aplicado al FORMATO** — mismo espíritu que el consenso de reconstrucción
(cierre §1) aplicado a los bytes del contenedor, no al build. Barato: un script de barrido
@@ -147,7 +147,7 @@ El único export de código real de FreeBSD que todo el mundo usa.
(Etapa G) dominan estrictamente para musl-Linux.
- **FreeBSD como builder de consenso multi-kernel** — sin namespaces/bwrap/Landlock allá; el
consenso multi-kernel barato ya existe (kernel propio vs genérico).
- **bectl / ZFS boot environments**`hammer boot menu` + `activate --from-select` ya cubren
- **bectl / ZFS boot environments**`takana boot menu` + `activate --from-select` ya cubren
el concepto; como mucho, robar UX de nombres/rollback más adelante.
- **bmake** — el mundo Linux necesita gmake; no reduce la deuda declarable de `/usr/bin/make`.
+8 -8
View File
@@ -1,7 +1,7 @@
# Plan — juegos en hammer (kernel, nativos, y la jaula glibc)
# Plan — juegos en takana (kernel, nativos, y la jaula glibc)
**Fecha:** 2026-07-16 · **Contexto:** hammer es musl + estático + fuente; el mundo del juego en
Linux es glibc + 32-bit + binarios ajenos (Steam/Proton). Este plan separa lo que hammer puede
**Fecha:** 2026-07-16 · **Contexto:** takana es musl + estático + fuente; el mundo del juego en
Linux es glibc + 32-bit + binarios ajenos (Steam/Proton). Este plan separa lo que takana puede
hacer *nativo y barato* (capas 12) de lo que exige un runtime ajeno *contenido y sellado*
(capa 3), en vez de fingir que musl va a correr Steam.
@@ -35,7 +35,7 @@ la misma línea:
Re-sellar linux-metal re-hashea el kernel ⇒ pasa por la granja y el ciclo QEMU-boot de
verificación existente, como el 2b7c701.
## Capa 2 — userspace nativo (sí compila en hammer)
## Capa 2 — userspace nativo (sí compila en takana)
- **T2.1 — schedulers scx (`scx_lavd`, `scx_bpfland`)**: los schedulers de juego de CachyOS.
Son programas BPF con userspace en Rust/C — pero compilar la parte BPF exige clang+libbpf ⇒
@@ -49,7 +49,7 @@ verificación existente, como el 2b7c701.
perfil. gamemoded habla D-Bus — la cadena KDE ya lo trajo, la dep existe.
- **T2.4 — mesa con RADV**: el driver Vulkan serio es AMD (RADV/ACO). Revisar features de
`recipes/mesa.toml` y activar RADV si no está. nouveau NO es para jugar (sabido por mirada);
NVK madura pero no este trimestre. El perfil de juego de hammer es honesto: **AMD primero**.
NVK madura pero no este trimestre. El perfil de juego de takana es honesto: **AMD primero**.
- **T2.5 — variantes v3 de mesa/wine**: donde el truco CachyOS paga de verdad para juegos.
Depende del piloto de `plan-variantes-cpu.md` (T5→T1T4 de ese plan); no se duplica aquí.
@@ -65,7 +65,7 @@ verificación existente, como el 2b7c701.
Steam, Proton y el catálogo de juegos son binarios glibc, parte 32-bit. musl no los va a
correr y perseguir eso es un pozo. La respuesta con ADN hammer: **el runtime ajeno se trae
correr y perseguir eso es un pozo. La respuesta con ADN takana: **el runtime ajeno se trae
entero, pineado por hash, y corre enjaulado** — verificar, no confiar, aplicado a un rootfs
que no construimos.
@@ -75,9 +75,9 @@ que no construimos.
- **F1 — la jaula propia**: importar el **Steam Linux Runtime** (sniper/pressure-vessel — el
contenedor que Valve ya usa para Proton) como artefacto `file_drop` content-addressed:
tarball pineado por sha256 en receta, sellado en el store, hidratable como cualquier
artefacto. Un `hammer-juego` (script primero) lo monta con bwrap + `/dev/dri` + ntsync +
artefacto. Un `takana-juego` (script primero) lo monta con bwrap + `/dev/dri` + ntsync +
sockets wayland/pipewire y lanza Steam adentro. Diferencia con F0: la cadena de custodia es
de hammer (hash en el índice firmado, no un remote de flathub).
de takana (hash en el índice firmado, no un remote de flathub).
- **F2 — (sólo si F1 duele) glibc propio**: construir glibc + multilib 32-bit desde fuente es
una campaña entera (toolchain dual, loader, locales). El mundo se mueve además hacia
**WoW64** (wine ≥9 corre 32-bit sobre wine 64-bit sin multilib), que puede volver F2
+10 -10
View File
@@ -2,11 +2,11 @@
**Fecha:** 2026-07-16 · **Origen:** el linaje Solus (hwcap dirs AVX2 ~2017, lista corta medida)
→ Red Hat (niveles x86-64-v2/v3/v4 + `glibc-hwcaps` en glibc 2.33, selección en el loader)
→ CachyOS (repos enteros v3/v4 + LTO + scheduler). Análisis de qué forma toma eso en hammer.
→ CachyOS (repos enteros v3/v4 + LTO + scheduler). Análisis de qué forma toma eso en takana.
**Tesis:** hammer ya tiene la mitad difícil gratis. Una variante optimizada no es un mecanismo
**Tesis:** takana ya tiene la mitad difícil gratis. Una variante optimizada no es un mecanismo
nuevo: es **la misma receta con otros `flags`**, que sella a otro hash en el mismo store. Lo que
las distros binarias resuelven con subdirectorios mágicos del loader, hammer lo resuelve con lo
las distros binarias resuelven con subdirectorios mágicos del loader, takana lo resuelve con lo
que ya es: contenido direccionado por hash + hydrate.
---
@@ -16,16 +16,16 @@ que ya es: contenido direccionado por hash + hydrate.
El mecanismo `glibc-hwcaps` es del loader de **glibc**. El ldso de musl no busca en
subdirectorios por microarquitectura y no va a hacerlo. Conclusión estructural:
> **La selección de variante en hammer ocurre en HYDRATE (por máquina), no en runtime (por
> **La selección de variante en takana ocurre en HYDRATE (por máquina), no en runtime (por
> proceso).** Cada máquina hidrata la variante que su CPU soporta; el árbol instalado tiene UNA
> copia de cada lib, la correcta. Sin dispatch, sin dobles copias en disco, sin loader parcheado.
Esto no es una limitación disfrazada: es el modelo más simple de los dos, y el único compatible
con el resto de hammer (un artefacto = un hash = un contenido; "elige en runtime" rompería la
con el resto de takana (un artefacto = un hash = un contenido; "elige en runtime" rompería la
correspondencia instalado↔sellado que usa todo, del journal a CVE-por-grafo).
Costo honesto: una imagen/USB "para cualquier máquina" debe llevar baseline (o dos hydrates).
El punto de selección es `hammer hydrate` / el instalador, con sonda de CPU (`cpuid` → nivel).
El punto de selección es `takana hydrate` / el instalador, con sonda de CPU (`cpuid` → nivel).
## 2. Mecanismo: variante = flags, cero framework nuevo
@@ -39,7 +39,7 @@ la ISA del builder. Una variante v3 es el mismo contrato con otro valor pineado:
- **T2 — `mcpu=x86_64_v3` pineado por nombre de nivel**, nunca `native` (mismo argumento que
baseline: la ISA del builder no puede filtrarse; el nivel es el contrato).
- **T3 — el índice firmado publica `(nombre, versión, nivel, hash)`**. El nivel es un eje más
del catálogo, no un repo aparte (donde CachyOS mantiene N repos, hammer tiene N hashes).
del catálogo, no un repo aparte (donde CachyOS mantiene N repos, takana tiene N hashes).
- **T4 — sonda de nivel en hydrate/instalador**: detectar v2/v3/v4 y resolver el hash
correspondiente, con fallback a baseline. Idempotente como todo hydrate.
@@ -54,7 +54,7 @@ puñado de libs con kernels vectorizables. Lista caliente inicial (todas ya en e
| candidata | por qué |
|---|---|
| `zlib`, `zstd`, `xz` | (de)compresión en cada operación del propio hammer |
| `zlib`, `zstd`, `xz` | (de)compresión en cada operación del propio takana |
| `mesa` | el caso con más ganancia medible en GUI/juegos (llvmpipe/ACO vectorizan) |
| códecs (ffmpeg-clase, cuando entren) | el caso clásico AVX2 |
| `pixman`, `cairo` | raster 2D — **frontera GUI: sólo en worker** (gotcha zig-skew) |
@@ -82,9 +82,9 @@ puñado de libs con kernels vectorizables. Lista caliente inicial (todas ya en e
- **Repo entero v3/v4** — coste de granja ×N por ganancia marginal fuera de la lista caliente.
- **Dispatch en runtime / hwcaps** — no existe en musl y el modelo por-máquina es mejor para
hammer de todos modos (§1).
takana de todos modos (§1).
- **`-mcpu=native` jamás** — ni en variantes: el nivel nombrado es el contrato reproducible.
- **LTO generalizado como campaña**hammer ya linkea estático con zig; el LTO por-receta se
- **LTO generalizado como campaña**takana ya linkea estático con zig; el LTO por-receta se
evalúa donde el benchmark de T5/T6 lo pida, no como política global.
## Orden propuesto
+25 -25
View File
@@ -1,9 +1,9 @@
# Runbook — el armador de kernel (`hammer kernel`)
# Runbook — el armador de kernel (`takana kernel`)
Implementa `docs/22-configurador-kernel.md` (SDD 22), que contesta
`tawasuyu/HANDOFF-KERNEL-CONFIG-A-HAMMER.md`.
**La regla que no se cruza:** hammer **lee** el grafo de Kconfig, no lo resuelve. El `.config` lo
**La regla que no se cruza:** takana **lee** el grafo de Kconfig, no lo resuelve. El `.config` lo
sigue produciendo el `olddefconfig` del propio kernel. Lo que la app emite son **fragmentos**
(`scripts/config -e/-d`) y una **receta derivada**.
@@ -24,7 +24,7 @@ curl -sSLo work/tarballs/linux-6.16.12.tar.gz \
mkdir -p work/kconfig-6.16.12
tar -xzf work/tarballs/linux-6.16.12.tar.gz -C work/kconfig-6.16.12 --strip-components=1 \
--wildcards '*/Kconfig*' '*/Makefile' '*/Makefile.*' 'linux-6.16.12/arch/x86/configs/*'
export HAMMER_KCONFIG_ROOT=work/kconfig-6.16.12
export TAKANA_KCONFIG_ROOT=work/kconfig-6.16.12
```
`work/` está en `.gitignore`. El `sha256` del tarball es el mismo que pinea `recipes/linux.toml`.
@@ -32,7 +32,7 @@ export HAMMER_KCONFIG_ROOT=work/kconfig-6.16.12
Control de salud del lector — **si esto no da 0 avisos, no confíes en lo que sigue**:
```sh
hammer kernel stats
takana kernel stats
# ficheros 1646 · símbolos 18212 (15169 visibles) · select 15165 · imply 445 · avisos 0
```
@@ -41,7 +41,7 @@ hammer kernel stats
Cero build, cero riesgo. Lee `/proc/config.gz` (o `/boot/config-<release>`).
```sh
hammer kernel probe
takana kernel probe
```
Contesta tres cosas: cuánto de cada bundle ya rige, qué capacidad carga este kernel que esta máquina
@@ -54,11 +54,11 @@ Si el config vivo y el catálogo son de series distintas, lo avisa: las clausura
## 2. El catálogo
`docs/state/kernel-bundles.toml`. Un bundle **no es una lista de símbolos**: es una raíz y su
clausura. `disable = ["WIRELESS"]` son ~400 `CONFIG_*` que hammer calcula.
clausura. `disable = ["WIRELESS"]` son ~400 `CONFIG_*` que takana calcula.
```sh
hammer kernel bundles # el catálogo con las clausuras resueltas
hammer kernel bundles --check # falla si envejeció respecto de este árbol
takana kernel bundles # el catálogo con las clausuras resueltas
takana kernel bundles --check # falla si envejeció respecto de este árbol
```
`--check` es el control de frescura y hace dos cosas:
@@ -72,7 +72,7 @@ hammer kernel bundles --check # falla si envejeció respecto de este árbol
Para decidir qué hacer con una fuga:
```sh
hammer kernel closure WIRELESS --fixpoint
takana kernel closure WIRELESS --fixpoint
```
El punto fijo **reporta el precio, no lo aplica**. Cerrar «sin audio» exige apagar
@@ -82,7 +82,7 @@ decisión, no una limpieza. Se resuelve una vez, con `close_leaks` o `accept_lea
## 3. Planear
```sh
hammer kernel plan --recipe recipes/linux.toml \
takana kernel plan --recipe recipes/linux.toml \
--bundle sin-wifi --bundle sin-audio --bundle solo-ext4 \
--knob jaula-y-eio-moderna \
--out work/kernel-plans/plan.json \
@@ -96,13 +96,13 @@ construir**.
⚠ Para construir la receta derivada hay que dejarla **junto a la receta base**, o sus `deps.build`
no resuelven. `plan` no escribe en `recipes/` por su cuenta: ese directorio es el corpus compartido.
⚠ **Y al construirla, `hammer build` va SIEMPRE bajo el lock compartido:**
⚠ **Y al construirla, `takana build` va SIEMPRE bajo el lock compartido:**
```sh
flock work/.farm-build.lock ./target/release/takana --store ./store build recipes/linux-derivada.toml
```
`hammer build` comparte `work/sources/<dep>-<sha>` entre todas las recetas. Dos builds simultáneos
`takana build` comparte `work/sources/<dep>-<sha>` entre todas las recetas. Dos builds simultáneos
que compartan una dependencia se pisan: uno hace fetch y borra el árbol mientras el otro lo usa, y
**el árbol queda roto para siempre** — reintentar no lo arregla. Medido a escala: al invalidar
`libdrm`, ~93 de 205 recetas KDE murieron con «`/src/.zwrap/cc` is not a full path to an existing
@@ -110,14 +110,14 @@ compiler tool», que es el wrapper de zig que la receta deja **en el árbol de f
fetch concurrente de otra. Es el ADR 0012, todavía sin decidir.
Es el mismo fichero de lock que toman `scripts/farm/farm-worker-loop.sh` y `campana-deuda.sh`, así
que con esto quedan serializados los tres. Deliberadamente **no** está dentro de `hammer build`: los
scripts de la granja ya lo toman por fuera y hammer se bloquearía contra ellos.
que con esto quedan serializados los tres. Deliberadamente **no** está dentro de `takana build`: los
scripts de la granja ya lo toman por fuera y takana se bloquearía contra ellos.
Nada más del armador toca el store: `plan` calcula el `ArtifactHash` con `hammer_build::artifact_hash`,
que es cómputo puro sobre las recetas, y `probe`/`gate`/`bundles`/`closure` sólo leen.
**Si el build muere con «no encuentro el ejecutable zig en …», el zig está bien: falta ESA versión.**
hammer lo busca por **directorio versionado** (`.dev-fs/tools/zig-x86_64-linux-<ver>/`), no por el
takana lo busca por **directorio versionado** (`.dev-fs/tools/zig-x86_64-linux-<ver>/`), no por el
symlink `tools/zig` ni por el `PATH`. `linux.toml` no pinea `zig_version`, pero tres de sus
`deps.build` sí — `flex`, `openssl` y `elfutils`, las tres a **0.13.0** — y la receta derivada las
hereda enteras.
@@ -131,13 +131,13 @@ consola serie. Por eso el gate **no corre sin `--objective`**.
En la máquina **destino** (que no tiene por qué ser la de build):
```sh
hammer kernel hw --out work/kernel-plans/hw.json
takana kernel hw --out work/kernel-plans/hw.json
```
En la de build:
```sh
hammer kernel gate --plan work/kernel-plans/plan.json \
takana kernel gate --plan work/kernel-plans/plan.json \
--objective qemu-serial --devices work/kernel-plans/hw.json
```
@@ -158,7 +158,7 @@ tar -xzf work/tarballs/linux-6.16.12.tar.gz -C work/kernel-test # el árbol E
cd work/kernel-test/linux-6.16.12
python3 -c "import json;print(json.load(open('../../kernel-plans/plan.json'))['configure'])" > /tmp/cfg.sh
bash /tmp/cfg.sh # defconfig + fragmento + olddefconfig
hammer kernel diff-back --plan ../../kernel-plans/plan.json --config .config
takana kernel diff-back --plan ../../kernel-plans/plan.json --config .config
```
Necesita `gcc`, `make`, `flex`, `bison` y `perl` en el host (`bc` sólo hace falta para compilar).
@@ -173,7 +173,7 @@ plan no apagaría nada y la predicción no se pondría a prueba. Es el error que
armador de punta a punta. Reproducirlo:
```sh
hammer kernel plan --recipe recipes/linux-metal.toml \
takana kernel plan --recipe recipes/linux-metal.toml \
--bundle sin-wifi --bundle sin-bluetooth --bundle sin-nfc --bundle sin-radios \
--bundle sin-audio --bundle sin-camaras-tv --bundle sin-infiniband --bundle sin-bus-industrial \
--bundle sin-thunderbolt-firewire --bundle sin-lector-de-tarjetas --bundle sin-virtualizacion \
@@ -208,7 +208,7 @@ capacidad).
Sin esto la UI miente: un `-e FOO` cuya dependencia no se cumple se pierde en silencio.
```sh
hammer kernel diff-back --plan work/kernel-plans/plan.json \
takana kernel diff-back --plan work/kernel-plans/plan.json \
--config <artefacto>/boot/config-6.16.12
```
@@ -224,10 +224,10 @@ la letra y no tener `CONFIG_MEMCG`, que nunca estuvo en ningún plan porque nadi
§4: por eso `memory.max` no existe y arje escribe al vacío dejando sólo un `warn!`).
```sh
hammer --store ./store kernel contract --sealed # todos los kernels sellados del store
hammer kernel contract --config <art>/boot/config-7.1.2 # uno suelto (el perfil sale del nombre)
hammer kernel contract --profile anfitrion-cards # el kernel VIVO de esta máquina
hammer kernel contract --list # qué promete hammer, y quién lo consume
takana --store ./store kernel contract --sealed # todos los kernels sellados del store
takana kernel contract --config <art>/boot/config-7.1.2 # uno suelto (el perfil sale del nombre)
takana kernel contract --profile anfitrion-cards # el kernel VIVO de esta máquina
takana kernel contract --list # qué promete takana, y quién lo consume
```
Sale distinto de cero si falta una capacidad **exigida por el perfil**. Tres cosas que conviene
@@ -252,7 +252,7 @@ bueno tenerlo» pertenece a un bundle del catálogo, no acá.
- **Sonda de arranque en VM** (#4 del handoff): construir → bootear en QEMU con el perfil de la
máquina destino → recién ahí dar de alta en el árbol de generaciones.
- **Atestación por huella** (#3): `hammer kernel hw` ya da la huella y el plan ya da el
- **Atestación por huella** (#3): `takana kernel hw` ya da la huella y el plan ya da el
`ArtifactHash`; falta el eje `fingerprint` en `PackageEntry` y el quórum de `minga atestar`. Y
ojo: «booteó» no basta para atestar — la atestación tiene que llevar **qué se comprobó**, que es
el mismo dato que calcula el gate.
+15 -15
View File
@@ -1,7 +1,7 @@
# Runbook — COSMIC, el cuarto escritorio
Estado al 2026-08-03: **LA SESIÓN COSMIC ARRANCA ENTERA.** `cosmic-session` —el camino de producción,
no el andamio— levanta la cadena completa sobre el kernel de hammer y arje-zero como PID1:
no el andamio— levanta la cadena completa sobre el kernel de takana y arje-zero como PID1:
```
cosmic-comp → cosmic-settings-daemon → cosmic-notifications → cosmic-panel
@@ -15,7 +15,7 @@ que la tabla de `.expect` de más abajo predijo, verificado en vez de supuesto.
![sesión COSMIC en QEMU](../evidencia/cosmic-session-qemu-2026-08-03.png)
Y el paso anterior: `cosmic-comp` arranca sobre el
kernel de hammer con arje-zero como PID1, toma el DRM, expone `wayland-1` y presenta frames; y
kernel de takana con arje-zero como PID1, toma el DRM, expone `wayland-1` y presenta frames; y
`cosmic-bg` se conecta, entrega su buffer y el fondo aparece **con el color exacto que se le
configuró** — `(0.13,0.29,0.53)``(33,74,135)`. O sea que la cadena cliente→compositor→KMS funciona
de punta a punta. Este documento se escribe mientras se hace, no después.
@@ -30,7 +30,7 @@ El paso anterior —el compositor solo, gris `(39,41,42)` y el cursor, 134 color
diferencia entre las dos imágenes es exactamente la pregunta que el modo `bare` separa.
COSMIC es el escritorio de System76, en Rust sobre **smithay** (compositor) e **iced/libcosmic**
(clientes). Es el cuarto de hammer, tras [mirada](mirada-usb-nvidia.md),
(clientes). Es el cuarto de takana, tras [mirada](mirada-usb-nvidia.md),
[KDE Plasma 6](kde-qemu-desktop.md) y [GNOME](gnome-qemu-desktop.md).
## Por qué NO se parece a los dos anteriores
@@ -73,7 +73,7 @@ Las deps se resuelven **hermano → padre**: desde `incoming-cosmic/` se ven las
**Copiar es gratis sólo si TODA la clausura transitiva resuelve a lo mismo.** El ArtifactHash no
depende de la ruta de la receta, así que un fichero idéntico *cuyas deps resuelven igual* da el mismo
hash ⇒ cache hit sobre el artefacto que ya existe. Se cumplió con `libdisplay-info`, `hwdata` y
`nasm`, y se verificó con `hammer hash` **antes** de copiar (`b3:260f0519`, `b3:bdc36cf9`,
`nasm`, y se verificó con `takana hash` **antes** de copiar (`b3:260f0519`, `b3:bdc36cf9`,
`b3:33383070`), no después. Dónde deja de cumplirse —y por qué importa— está en la sección de
`cosmic-settings-daemon`, más abajo.
@@ -367,7 +367,7 @@ cd ~/hammer
las entradas `.desktop` del rootfs;
- fondo `(33,74,135)` = exactamente el `Color(Single((0.13,0.29,0.53)))` que escribe `cosmic-start`.
48 procesos `cosmic-*` estables a los 30 s, sobre el kernel de hammer con arje-zero como PID1.
48 procesos `cosmic-*` estables a los 30 s, sobre el kernel de takana con arje-zero como PID1.
Se reproduce así (headless y automatizable; **el `-vga none` no es opcional**, ver gotcha 12):
```sh
@@ -387,7 +387,7 @@ impecable y la pantalla estaba lisa.
## 🖱 LA ENTRADA: se usa, no sólo se ve (2026-08-04)
**Hay que usar USB HID, no virtio-input.** El kernel de hammer no trae `VIRTIO_INPUT` (mirá el
**Hay que usar USB HID, no virtio-input.** El kernel de takana no trae `VIRTIO_INPUT` (mirá el
`-e …` de `recipes/linux-generic.toml`), así que `-device virtio-keyboard` no produce ni un
`/dev/input/event*`. Sí trae `USB_XHCI_HCD` + `USB_HID` + `INPUT_EVDEV`, y además es lo que se va a
usar en metal, así que la prueba es más fiel:
@@ -562,7 +562,7 @@ Linux 6.16.12 x86_64
```
Una ventana de verdad —con decoración, *File/Edit/View*, minimizar/maximizar/cerrar—, `bash` corriendo
adentro, el **kernel propio de hammer** contestando, y el `qalc` que empaquetamos una hora antes
adentro, el **kernel propio de takana** contestando, y el `qalc` que empaquetamos una hora antes
devolviendo 42. Se abre haciendo click en su icono en la biblioteca de aplicaciones
(`cosmic-biblioteca-con-app-qemu-2026-08-04.png`), que hasta hoy salía vacía **con razón**: los 20
`.desktop` de la imagen eran `NoDisplay=true`. Éste no lo es, y es el primero.
@@ -586,7 +586,7 @@ que entren `vim` o `htop` en la imagen, hará falta un paquete de datos de termi
### 📁 `cosmic-files`: «ya está compilado» era falso por una línea del vecino
`docs/evidencia/cosmic-files-qemu-2026-08-04.png`: el gestor abre desde la biblioteca y **lista el
sistema de ficheros real de hammer** — `bin` (72 ítems), `dev` (129), `ente`, `etc`, `lib`,
sistema de ficheros real de takana** — `bin` (72 ítems), `dev` (129), `ente`, `etc`, `lib`,
`lost+found`—, con su barra lateral de Recientes/Carpeta personal/Papelera.
Entró creyendo que era casi gratis, porque `cosmic-term` **ya compila cosmic-files como crate**. No
@@ -679,7 +679,7 @@ completo da ~1670: el umbral es «cientos vs. miles», no un número exacto.)
`docs/evidencia/cosmic-edit-qemu-2026-08-04.png` y `…-escribiendo-…png` — 1280×800, **2105 colores**:
ventana con menús *File/Edit/View*, pestaña *New document*, numeración de líneas, y el icono del
editor en el dock con el punto de «en ejecución». La segunda captura tiene `hammer edita` escrito
editor en el dock con el punto de «en ejecución». La segunda captura tiene `takana edita` escrito
por teclado sobre la línea 1 y el punto de modificado en la pestaña: **se escribe, no sólo se ve**.
Se llegó por el lanzador, sin tocar el ratón para elegir: `Super` → teclear `text` → el primer
@@ -702,7 +702,7 @@ mínimo de la suite.
(App Library, Files, Launcher, Settings) y *Recently updated*; y `…-busqueda-…png` con la búsqueda
`text` devolviendo *COSMIC Text Editor*. Sellada en `b3:a06437f7`, NEEDED = `libxkbcommon` + `libc`.
**Es la primera aplicación de la suite que choca con que hammer es OTRA distro.** Una tienda es la
**Es la primera aplicación de la suite que choca con que takana es OTRA distro.** Una tienda es la
cara de un gestor de paquetes. De sus cuatro backends (`src/backend/`, todos tras `#[cfg(feature)]`):
`flatpak` pide `libflatpak` (GObject ⇒ glib compartida + ostree/libsoup/gpgme: la torre de C que esta
campaña evita); `packagekit` es Rust puro pero necesita el DEMONIO en el bus; `rpm-ostree` es
@@ -775,7 +775,7 @@ patrón que ya bloqueó `cosmic-files-applet`, y acá es **un parche de una lín
### ⚠ CORRECCIÓN: la cadena glib COMPARTIDA YA EXISTE Y ESTÁ SELLADA
Este documento decía que `gvfs` exigía «una campaña propia». **Es falso desde que la campaña KDE
selló las piezas.** Medido con `hammer hash` desde las dos colas, que es el método correcto para
selló las piezas.** Medido con `takana hash` desde las dos colas, que es el método correcto para
saber si una receta se puede compartir:
| receta | cola | ¿resuelve igual desde `incoming-cosmic`? |
@@ -1029,7 +1029,7 @@ SENDER**, así que `CreateSession` con un `dbus-send` y `SelectSources` con otro
de nombres únicos distintos. Hace falta UNA conexión viva durante todo el intercambio.
`ashpd` era el camino obvio, pero el árbol fuente de una receta de código propio es el repo del
**propio hammer** (no hay modo `path` en `[source]`: sólo git y tarball) ⇒ habría sumado ~150 crates
**propio takana** (no hay modo `path` en `[source]`: sólo git y tarball) ⇒ habría sumado ~150 crates
al `Cargo.lock` de la herramienta de build por una sonda de diagnóstico. En C sobre `libdbus` cuesta
**cero deps nuevas**, sale **estática (0 NEEDED)** —un instrumento no debe depender de lo que mide— y
sobre todo: **el protocolo se ve**. Una sonda cuyo valor es documentar un handshake no debería
@@ -1242,7 +1242,7 @@ Dos causas encadenadas, y la segunda es la que hizo perder el viaje:
1. **CARRERA contra la enumeración USB.** El `rdinit` arranca apenas se desempaqueta el initramfs,
pero el bus USB enumera **asíncrono** y tarda segundos (reset de hub, *settling* de 1 s por
dispositivo, scan SCSI). El pivote reintentaba `findfs LABEL=hammer-root` 5 veces con 1 s. En esa
dispositivo, scan SCSI). El pivote reintentaba `findfs LABEL=takana-root` 5 veces con 1 s. En esa
máquina el disco es `sd 6:0:0:0` —séptimo host SCSI— y no llegó a tiempo. **En QEMU el disco es
virtio y está desde el instante cero, así que esta carrera NO SE PUEDE VER en validación**: hay
que arrancar la imagen como `usb-storage` para reproducirla. Es el `rootwait` que no podemos usar,
@@ -1271,8 +1271,8 @@ qemu-system-x86_64 -m 4096 -enable-kvm -cpu host -display none -vga std -serial
-qmp unix:/tmp/qmp.sock,server,nowait # el veredicto es el screendump, no el log
```
De paso: el rebuild avisa `! sin hammer musl`**el CLI `hammer` no está en la imagen** y
`hammer boot menu` nunca corre. Igual lleva `timeout 15`, porque corre ANTES del `exec` de arje-zero
De paso: el rebuild avisa `! sin takana musl`**el CLI `takana` no está en la imagen** y
`takana boot menu` nunca corre. Igual lleva `timeout 15`, porque corre ANTES del `exec` de arje-zero
y colgarse ahí deja un arranque sin PID1 ni pantalla — el `|| true` protege del fallo, no del bloqueo.
### 🖥 2º viaje: la imagen forzaba SOFTWARE — no podía ganar ni en el mejor caso (2026-08-05)
+4 -4
View File
@@ -1,6 +1,6 @@
# Runbook — GNOME en QEMU: el escritorio PINTA
Estado al 2026-07-29: **cerrado.** gnome-shell arranca desde fuente sobre el kernel de hammer, toma
Estado al 2026-07-29: **cerrado.** gnome-shell arranca desde fuente sobre el kernel de takana, toma
el DRM master y los tres dispositivos de input por logind, expone `wayland-0`, y **dibuja el Overview
de GNOME**. Cinco muros caídos, cada uno por medición y no por conjetura:
@@ -37,7 +37,7 @@ decía «sesión viva» y la pantalla estaba negra.
El `gnome-start` deja el core en la raíz ext4 y hace `sync`, así que sobrevive al SIGKILL de QEMU.
```sh
sfdisk -J work/hammer-gnome-qemu.img # localizar hammer-root (part 2)
sfdisk -J work/hammer-gnome-qemu.img # localizar takana-root (part 2)
dd if=work/hammer-gnome-qemu.img of=/tmp/root.img bs=1M skip=129 count=5113 status=none
debugfs -R "dump /core.gnome-shell.<pid> /tmp/core.gnome-shell" /tmp/root.img
gdb -batch -q -ex "set sysroot $PWD/work/gnome-qemu-rootfs" \
@@ -48,7 +48,7 @@ gdb -batch -q -ex "set sysroot $PWD/work/gnome-qemu-rootfs" \
![gnome-shell en QEMU](../evidencia/gnome-shell-qemu-2026-07-29.png)
El Overview de GNOME Shell, construido entero desde fuente sobre el kernel de hammer, arje-zero como
El Overview de GNOME Shell, construido entero desde fuente sobre el kernel de takana, arje-zero como
PID1 y musl: el conmutador de espacios de trabajo arriba a la izquierda, el reloj en el panel, la
miniatura del escritorio, el dash abajo, y la notificación estándar de GNOME por sesión de root
(«Logged in as a privileged user»).
@@ -383,7 +383,7 @@ elige `/dev/dri/card0` como primaria sin quejarse. Es el rédito directo de la c
## Lo que falta, en orden
1. ✅ **HECHO** — los seis están sellados y en la cola GNOME (verificado por `hammer hash`, 2026-08-03):
1. ✅ **HECHO** — los seis están sellados y en la cola GNOME (verificado por `takana hash`, 2026-08-03):
`gnome-desktop` b3:b3aed0d6 · `geoclue` b3:50e9de94 · `libgweather` b3:d289ac66 · `ibus` b3:d4d3750a ·
`librsvg` b3:351f4658 · `upower` b3:9c31f20f. Es lo que hace que el shell arranque; el arranque de
hoy ve **59 typelibs de sistema + 3 del shell**. El texto original del plan queda abajo por el
+3 -3
View File
@@ -1,7 +1,7 @@
# Runbook — relanzar el escritorio KDE Plasma 6 en QEMU
Plasma 6 renderizado 100% por software (QPainter + llvmpipe) sobre `virtio-gpu`, en la cadena
soberana de hammer (LLVM 18 + mesa desde fuente). Sin GPU real. Cerrado 2026-07-20 tras resolver
soberana de takana (LLVM 18 + mesa desde fuente). Sin GPU real. Cerrado 2026-07-20 tras resolver
5 bugs estructurales + 2 de cursor (ver [[kde-metal-qemu-desktop]] en memoria y `git log` del frente
`kde/qemu:`). Este runbook es sólo para **relanzar y verificar**, no para rediagnosticar.
@@ -161,12 +161,12 @@ los módulos QML con `qmldir` que hay en el rootfs; así salió `org.kde.coreadd
### El arreglo, verificado con UN build (2026-09-03)
`-DKCOREADDONS_USE_QML=ON` + **un solo** `hammer build kcoreaddons` (56 s) instala
`-DKCOREADDONS_USE_QML=ON` + **un solo** `takana build kcoreaddons` (56 s) instala
`usr/lib/qt6/qml/org/kde/coreaddons/{libkcoreaddonsplugin.so,qmldir,…}`. Hidratado ESE artefacto en
el rootfs y rehecha la imagen, el menú **abre**: usuario, buscador, Favoritos / Todas las
aplicaciones, y las pestañas Aplicaciones / Lugares / Sesión
(`../evidencia/plasma6-qemu-2026-09-03-menu-abre.png`). Escribiendo `konsole` en el buscador y Enter,
la terminal arranca y corre — `uname -a` da el kernel 6.16.12 de hammer y `konsole --version` da
la terminal arranca y corre — `uname -a` da el kernel 6.16.12 de takana y `konsole --version` da
25.04.3 (`../evidencia/plasma6-qemu-2026-09-03-konsole-uname.png`).
Es la cadena completa del escritorio por primera vez: **panel → menú → búsqueda → app → shell**.
+13 -13
View File
@@ -1,7 +1,7 @@
# Runbook — auto-alojamiento del toolchain (variante b, cerrada)
Cierre de la **variante (b)** del SDD 11 §7.2: el toolchain del builder ya no es "todo Alpine" —
cada pieza genuinamente en-camino se construye **desde fuente con hammer** y el auto-alojamiento de
cada pieza genuinamente en-camino se construye **desde fuente con takana** y el auto-alojamiento de
Stage 1 sigue siendo **bit a bit** (`of_tree(stage1') == of_tree(stage1)`).
Este runbook documenta el end-state. Para el mecanismo base (rebuild in-rootfs, builder, QEMU) ver
@@ -11,10 +11,10 @@ Este runbook documenta el end-state. Para el mecanismo base (rebuild in-rootfs,
## 0. Qué demuestra / qué no
- **SÍ:** el builder reconstruye los 4/4 de Stage 1 (musl, busybox, hammerd, arje-zero) con un
toolchain **construido por hammer desde fuente** (make, busybox, coreutils, bwrap, linux-headers
toolchain **construido por takana desde fuente** (make, busybox, coreutils, bwrap, linux-headers
**y** rustc/cargo), y el `of_tree(stage1')` reproduce la referencia **bit a bit** dentro de la VM.
- **NO:** no afirma igualdad byte-a-byte con el toolchain de Alpine. El criterio es
**auto-consistencia** (el toolchain hammer reproduce SU propio sello), no equivalencia con Alpine
**auto-consistencia** (el toolchain takana reproduce SU propio sello), no equivalencia con Alpine
(ver §3, los dos anclajes de `of_tree`).
## 1. Las dos categorías de swap (clave conceptual)
@@ -27,7 +27,7 @@ Este runbook documenta el end-state. Para el mecanismo base (rebuild in-rootfs,
Por eso hay **dos anclajes** de `of_tree(stage1)`:
- **`b3:9adefb82…`** — baseline con **rustc de Alpine** 1.91.1 (los 5 tools de sandbox lo mantienen).
- **`b3:7fa6cb4e…`** (`RUST_EXPECT_REF`) — con **hammer-rust** 1.91.1 (mrustc→1.90→1.91.0→1.91.1).
- **`b3:7fa6cb4e…`** (`RUST_EXPECT_REF`) — con **takana-rust** 1.91.1 (mrustc→1.90→1.91.0→1.91.1).
≠ 9adefb82 porque el compilador emite los bytes; es auto-consistente (reproducido en host y VM).
## 2. Piezas del toolchain construidas desde fuente
@@ -52,17 +52,17 @@ Todas estáticas musl con `zig cc` salvo donde se note. "En-camino" = la usan lo
## 3. Corridas de verificación (todas ✓ REPRODUCIBLE in-VM)
Prerrequisitos: `./scripts/bootstrap-devfs.sh` (devfs Alpine + zig + g++ + musl 256-TLS-keys), KVM, y
para `SWAP_RUST` el prefix hammer-rust en `.scratch/rust-1.91.1-prefix` (ver el README del frente).
para `SWAP_RUST` el prefix takana-rust en `.scratch/rust-1.91.1-prefix` (ver el README del frente).
```sh
# (a) 5 tools de sandbox acumulados (rustc = Alpine) → of_tree 9adefb82
SWAP_MAKE=1 SWAP_BUSYBOX=1 SWAP_LINUX_HEADERS=1 SWAP_BWRAP=1 SWAP_COREUTILS=1 \
KVM=1 MEM=24576 ./scripts/selfhost-verify.sh
# (b) compilador hammer-rust solo → of_tree 7fa6cb4e
# (b) compilador takana-rust solo → of_tree 7fa6cb4e
SWAP_RUST=1 KVM=1 MEM=16384 PRESEED=hammerd ./scripts/selfhost-verify.sh
# (c) CAPSTONE — los 6 juntos (todo el toolchain en-camino hammer-built) → of_tree 7fa6cb4e
# (c) CAPSTONE — los 6 juntos (todo el toolchain en-camino takana-built) → of_tree 7fa6cb4e
SWAP_MAKE=1 SWAP_BUSYBOX=1 SWAP_LINUX_HEADERS=1 SWAP_BWRAP=1 SWAP_COREUTILS=1 \
SWAP_RUST=1 KVM=1 MEM=12288 PRESEED=hammerd ./scripts/selfhost-verify.sh
```
@@ -78,7 +78,7 @@ Resultados verificados:
arje-zero (monorepo tawasuyu) vendorea ~1973 crates; un rebuild in-VM completo (`PRESEED=all`)
desborda el rootfs en RAM de la VM (ENOSPC en `cargo vendor`). `PRESEED=hammerd` preseedea el
arje-zero host-built (hammer-rust, `--locked`) y sólo reconstruye hammerd in-VM (vendor chico). Con
arje-zero host-built (takana-rust, `--locked`) y sólo reconstruye hammerd in-VM (vendor chico). Con
esto la corrida (c) cabe en `MEM=12288` **sin swap del host**.
### Reproducibilidad de arje-zero (drift cerrado)
@@ -89,14 +89,14 @@ Cerrado fijando el `Cargo.lock` del workspace en tawasuyu (`recipes/arje-zero.to
## 4. El mecanismo de swap
`hammer bootstrap builder --swap name=<hash>[:rel_path]` (repetible; `rel_path` default
`takana bootstrap builder --swap name=<hash>[:rel_path]` (repetible; `rel_path` default
`usr/bin/<name>`) monta el artefacto sellado sobre el path del toolchain Alpine y lo ancla en el
**hash lógico** del builder (`swaps_digest`), haciendo la procedencia auditable. Variantes:
- **archivo** (make, busybox, coreutils, bwrap): estático musl ⇒ sin shim del loader.
- **directorio** (linux-headers): un `rel_path` directorio reemplaza el árbol entero (remove+copy,
borra huérfanos Alpine) — `assemble_builder` en `hammer-bootstrap`.
borra huérfanos Alpine) — `assemble_builder` en `takana-bootstrap`.
- **rust** (`swap-rust-into-toolchain.sh`): overlay aditivo+reversible de `.scratch/rust-1.91.1-prefix`
sobre `.dev-fs/alpine`; sólo reemplaza `/usr/bin/{rustc,cargo}` (el rustlib y `.so` de hammer no
sobre `.dev-fs/alpine`; sólo reemplaza `/usr/bin/{rustc,cargo}` (el rustlib y `.so` de takana no
chocan con los de Alpine). `--restore` deja el devfs prístino. Como el input-hash del store NO
incluye el rustc, `SWAP_RUST` usa un **store dedicado** (`store-rust`) para forzar el rebuild.
@@ -121,14 +121,14 @@ Cerrado fijando el `Cargo.lock` del workspace en tawasuyu (`recipes/arje-zero.to
pero es **inerte al `of_tree`**: el lab compila con `zig cc` (ensamblador interno + lld + `zig
ar`/`ranlib`/`objcopy`), así que los 4/4 nunca invocan el `ld`/`as` de binutils.
Verificado por bisección (host, sin VM, 2026-06-19): superponer hammer-binutils (`61400d05`) sobre
Verificado por bisección (host, sin VM, 2026-06-19): superponer takana-binutils (`61400d05`) sobre
`.dev-fs/alpine/usr/bin` (confirmando que el `ld` activo pasa a NEEDED `libc.so` sin `libbfd`) y
reconstruir musl+busybox → **byte-idénticos** al baseline (`57b66a2e`, `56664d70`). El swap no mueve
`of_tree`. (CC=gcc por el gotcha de zig; `link=dynamic` porque libtool descarta el `-static` del exe.)
## 7. Estado
**Frente de auto-alojamiento CERRADO.** Todo el toolchain en-camino es hammer-from-source y el
**Frente de auto-alojamiento CERRADO.** Todo el toolchain en-camino es takana-from-source y el
auto-alojamiento está verificado bit a bit con todas las piezas juntas (capstone (c)). No quedan
piezas del toolchain por de-Alpinizar (gcc nativo se usa sólo como bootstrap-compiler de las
herramientas grandes, igual status que zig; el veto purista era sobre un *rustc* prebuilt, ya
+23 -23
View File
@@ -1,7 +1,7 @@
# Runbook — validar Stage 1 booteando en QEMU
> Operativo, no diseño. Valida end-to-end que el rootfs que produce
> `hammer bootstrap stage1` ([SDD 11](../11-bootstrap.md)) **bootea con arje-zero como PID 1**
> `takana bootstrap stage1` ([SDD 11](../11-bootstrap.md)) **bootea con arje-zero como PID 1**
> ([SDD 12](../12-init-real.md)), levanta `hammerd` + getty bajo supervisión, y que el **`CRASHED`
> real** funciona.
>
@@ -21,12 +21,12 @@
## 1. Prerrequisitos
- **Lab de dev** montado: `./scripts/bootstrap-devfs.sh` (deja `.dev-fs/alpine` + `.dev-fs/tools/zig`).
- **Red**: el build de `hammerd` y `arje-zero` clona repos git (hammer, tawasuyu) y corre
- **Red**: el build de `hammerd` y `arje-zero` clona repos git (takana, tawasuyu) y corre
`cargo vendor` en el fetch. El build hermético del sandbox es `--offline`, pero el fetch necesita red.
- **Herramientas host**: `qemu-system-x86_64`, un kernel x86_64 (`/boot/vmlinuz-*`), `cpio`, `gzip`.
- **Espacio/tiempo**: construir `arje-zero` vendorea las deps del **monorepo tawasuyu entero** — es
pesado y lento la primera vez (se cachea después).
- **El binario `hammer`**: `cargo build --release -p hammer-cli``./target/release/takana`.
- **El binario `takana`**: `cargo build --release -p takana-cli``./target/release/takana`.
> El store debe vivir en la raíz del repo (`./store`) para que `BuildConfig` resuelva
> `.dev-fs/alpine` como hermano (`store/..` = repo root). El toolchain de Stage 1 **no** es el de
@@ -63,7 +63,7 @@ ls "$ROOTFS_DIR"/ente/seed.card.json "$ROOTFS_DIR"/sbin/init "$ROOTFS_DIR"/usr/b
```
Necesita red (git clone + `cargo vendor`). Si un build Cargo falla en el link (`-lgcc_s`) o por
static-PIE, ver §7 — el `zig cc` del lab debería resolver `-lgcc_s` (bitácora M2 del plan arje↔hammer).
static-PIE, ver §7 — el `zig cc` del lab debería resolver `-lgcc_s` (bitácora M2 del plan arje↔takana).
## 4. Empaquetar el rootfs como initramfs
@@ -174,7 +174,7 @@ correcto y golden-path es **crt-static** — compilar los componentes Rust con
`-C target-feature=+crt-static -C relocation-model=static` para binarios **autocontenidos** (sin
`libc.so`, sin loader, sin soname, como busybox). Requiere rebuild de hammerd/arje-zero (el de
arje-zero vendorea el monorepo: caro en tiempo y disco). Es la realización plena de la "vía de oro"
estática de hammer y cierra de paso el problema del soname de Alpine.
estática de takana y cierra de paso el problema del soname de Alpine.
**Actualización (Opción A implementada).** `BuildSys::Cargo` ahora construye **nativo** sin `--target`
cuando el target es el del sandbox (`SANDBOX_NATIVE_TARGET`), con un wrapper `.hammer-zig-cc` que
@@ -183,7 +183,7 @@ cuando el target es el del sandbox (`SANDBOX_NATIVE_TARGET`), con un wrapper `.h
**hammerd compila nativo y se sella ✓** — el camino Rust funciona. **arje-zero queda bloqueado** por
otra razón: **tawasuyu no commitea `Cargo.lock`** (`git ls-files Cargo.lock` vacío), así que el
`git archive` no lo trae y `cargo vendor --locked` no puede crearlo. Decisión pendiente: commitear
el lock en tawasuyu (reproducible, plan C.2 #5) vs relajar `--locked` en el vendoring de hammer.
el lock en tawasuyu (reproducible, plan C.2 #5) vs relajar `--locked` en el vendoring de takana.
**Cierres posteriores.** (1) Vendoring `--locked` **condicional** al lock committeado → arje-zero
vendorea sin `--locked` (genera el lock) y **se sella**: los 4 componentes buildan. (2) Boot:
@@ -203,7 +203,7 @@ QEMU (`-cpu Broadwell`) **levanta el sistema**:
```
arje_zero: ente-zero despierta como PID 1
arje_zero::seed: Tarjeta Semilla cargada y validada path=/ente/seed.card.json
arje_zero: Ente #0 entra al bucle primordial label=hammer-stage1
arje_zero: Ente #0 entra al bucle primordial label=takana-stage1
arje_zero::graph::lifecycle: instanciando genesis count=2
Ente encarnado label=hammerd pid=Some(Pid(59))
Ente encarnado label=console-getty pid=Some(Pid(60))
@@ -237,7 +237,7 @@ a la capa de IA por `agent.sock` es B.2 (bus único).
## 8b. Stage 2 — verificación de reproducibilidad (pre-rebuild-in-rootfs)
El `stage2` de hammer ancla el **content-hash** (`ArtifactHash::of_tree`, los bytes reales) del
El `stage2` de takana ancla el **content-hash** (`ArtifactHash::of_tree`, los bytes reales) del
rootfs y lo compara con un rebuild. El rebuild **dentro** del rootfs usando *sólo* las herramientas
de Stage 1 (auto-alojamiento pleno) necesita un **builder rootfs** — el Stage 1 mínimo es un
*runtime* (no trae zig/make/cargo), así que ese paso es un hito aparte. Lo que **sí** se verificó
@@ -248,7 +248,7 @@ acá: la **bit-reproducibilidad del build** (dos builds independientes del mismo
|---|---|---|
| **musl** 1.2.5 | ✅ byte-idéntico | el build C con zig cc es determinista de fábrica |
| **busybox** 1.36.1 | ✅ tras dos fixes | (ver abajo) |
| **hammerd** (Rust) | ✅ byte-idéntico | crt-static + cargo `--locked` + `SOURCE_DATE_EPOCH` + paths fijos `/src` + el workspace de hammer pinea **`codegen-units = 1`** ⇒ rust reproducible |
| **hammerd** (Rust) | ✅ byte-idéntico | crt-static + cargo `--locked` + `SOURCE_DATE_EPOCH` + paths fijos `/src` + el workspace de takana pinea **`codegen-units = 1`** ⇒ rust reproducible |
| **arje-zero** (Rust) | ✅ tras fix codegen-units | dos builds del host divergían ~10 KB repartidos por `.text`/`.rodata`/`.eh_frame`: el workspace tawasuyu **no** fija `[profile.release]` → cargo usa el default `codegen-units = 16` y el codegen paralelo de rustc no es reproducible. Fix: el sandbox impone `CARGO_PROFILE_RELEASE_CODEGEN_UNITS=1` para **todas** las crates (ver nota de cierre §8c). Verificado: dos builds cu=1 → byte-idénticos |
**La verificación cazó dos no-determinismos reales en busybox** (justo lo que [SDD 09 §2](../09-trust-model.md) pide):
@@ -270,26 +270,26 @@ pleno, un hito propio (el Stage 1 mínimo es runtime, sin compilador).
## 8c. Builder rootfs — el rebuild in-rootfs (auto-alojamiento, SDD 11 §7)
> **Variante (b) — auto-alojamiento puro — CERRADA.** Este §8c cubre el mecanismo con toolchain
> Alpine (variante a). Para el toolchain construido por hammer desde fuente (make/busybox/coreutils/
> Alpine (variante a). Para el toolchain construido por takana desde fuente (make/busybox/coreutils/
> bwrap/linux-headers + rust + binutils) y la verificación capstone de los 6 swaps juntos in-VM, ver
> [self-hosting-toolchain.md](self-hosting-toolchain.md).
El **ensamblado** del builder ya está implementado (`hammer bootstrap builder`, [SDD 11 §7.4](../11-bootstrap.md);
El **ensamblado** del builder ya está implementado (`takana bootstrap builder`, [SDD 11 §7.4](../11-bootstrap.md);
variante a, toolchain desde Alpine). Produce, sobre el Stage 1 rootfs, una imagen con el toolchain
adentro lista para reconstruirse a sí misma. Receta operacional:
**1. Binario `hammer` estático (musl).** El builder lo bootea como PID-algo en la VM, así que debe
**1. Binario `takana` estático (musl).** El builder lo bootea como PID-algo en la VM, así que debe
ser autocontenido (como hammerd/arje-zero, crt-static — §7):
```sh
cargo build --release --target x86_64-unknown-linux-musl -p hammer-cli
cargo build --release --target x86_64-unknown-linux-musl -p takana-cli
# (o cargo rustc -- -C target-feature=+crt-static, según el camino de oro del boot)
```
**2. Ensamblar el builder** anclando la referencia `of_tree(stage1)` (la que imprimió `stage2`):
```sh
hammer --store store bootstrap builder \
takana --store store bootstrap builder \
--stage1 b3:<STAGE1> --seed-hash b3:<SEED> \
--hammer-bin target/x86_64-unknown-linux-musl/release/hammer \
--toolchain .dev-fs/alpine --toolchain-tag alpine-3.23.4 \
@@ -335,10 +335,10 @@ rebuild-stage1
# ✓ REPRODUCIBLE: stage1' == stage1 (auto-alojado bit a bit) ← of_tree(stage1')==REF_CONTENT
```
`rebuild-stage1` apunta `HAMMER_ROOTFS=/toolchain`, corre `hammer bootstrap stage1` con la semilla y
`rebuild-stage1` apunta `TAKANA_ROOTFS=/toolchain`, corre `takana bootstrap stage1` con la semilla y
las recetas de adentro, y compara con `stage2 --verify $REF_CONTENT`. **✓ REPRODUCIBLE** cierra el
auto-alojamiento end-to-end de la variante (a); **✗ DIVERGENTE** caza un no-determinismo nuevo
([SDD 09 §2](../09-trust-model.md)). La variante (b) — el toolchain construido por hammer desde
([SDD 09 §2](../09-trust-model.md)). La variante (b) — el toolchain construido por takana desde
fuente — reemplaza `/toolchain` Alpine pieza a pieza, con este mismo lazo verificando cada paso.
### ✅ Rebuild in-rootfs ejecutado (2026-06-11): 2/4 componentes sellados en la VM
@@ -422,7 +422,7 @@ limpio, y se promovió a `./store` (el nativo quedó en `store-native-bak`). `ar
`of_tree(stage1)=b3:198f209f5e2c9d3a37411823b2ea42a09074d4e482f044278958b1b86f232dbb`
(rootfs key `b3:2bd7c91b…`; nótese que el key `of_inputs` es **idéntico** al del preseed-nativo —
el cambio de bytes de arje no movió su key: la misma escotilla C.2). El builder se re-ensambló con
ese toolchain + el `hammer` baseline + `--ref-content 198f209f…` (embebida en
ese toolchain + el `takana` baseline + `--ref-content 198f209f…` (embebida en
`/etc/hammer/rebuild.env`) y se repackó **`work/builder.cpio.gz`** (415 MB, 27 027 entradas,
`--owner=root:root`). **Listo para bootear:** `scripts/boot-builder-vm.sh` (o `scripts/drive-rebuild.py`
no-interactivo) → adentro `rebuild-stage1` debe reproducir **`198f209f…`** ⇒ **✓ REPRODUCIBLE**
@@ -436,7 +436,7 @@ bajo el mismo key `of_inputs`. La caza (bisección por componente en el host): `
(`of_tree`) es determinista e independiente del entorno. El culpable era **`arje-zero`**: dos builds
del **mismo** host daban binarios distintos (Δ ~9.6 KB repartidos por `.text` 8064, `.rodata` 896,
`.eh_frame`, `.gcc_except_table`, `.data.rel.ro`, `.got`) — la firma del **codegen paralelo** de
rustc. Raíz: el workspace de hammer pinea `[profile.release] codegen-units = 1` (por eso `hammerd`
rustc. Raíz: el workspace de takana pinea `[profile.release] codegen-units = 1` (por eso `hammerd`
reproducía), pero el monorepo tawasuyu **no** declara perfil → cargo usa el default `codegen-units = 16`.
Con 16 unidades el reparto del crate en N objetos varía build-a-build incluso con entradas idénticas,
y la salida cambia. **Fix:** el sandbox impone `CARGO_PROFILE_RELEASE_CODEGEN_UNITS=1` para todas las
@@ -457,12 +457,12 @@ confirmado en el bucle completo** (host↔VM), no sólo host↔host.
**✓ REPRODUCIBLE con swaps de la variante (b) — verificado end-to-end in-VM (2026-06-13).**
`KVM=1 MEM=24576 SWAP_MAKE=1 SWAP_BUSYBOX=1 ./scripts/selfhost-verify.sh` en `libre`: el builder se
ensambla pisando el `/toolchain` Alpine con el **make** (`b3:fbad44ac…`) y el **busybox**
(`b3:56664d70…`) que hammer compiló desde fuente, y la VM reconstruye los **4/4** con ese toolchain
hammerizado — incluido busybox compilándose a sí mismo con hammer-busybox de shell. Sellaron in-VM:
(`b3:56664d70…`) que takana compiló desde fuente, y la VM reconstruye los **4/4** con ese toolchain
hammerizado — incluido busybox compilándose a sí mismo con takana-busybox de shell. Sellaron in-VM:
`musl 57b66a2e…`, `busybox 56664d70…`, `hammerd d08fd273…`, `arje-zero bd0f8475…` (cu=1), y el
`of_tree(stage1')` **igualó la referencia** `9adefb82…``✓ REPRODUCIBLE: stage1' == stage1`,
`DRIVER_RC=0` (`RESULTADO: ✓ REPRODUCIBLE`, elapsed **3261 s** ≈ 54 min con `arje-zero` cu=1 in-VM).
**La procedencia del builder deja de ser "todo Alpine": make+busybox son ya de hammer, auditados, y
**La procedencia del builder deja de ser "todo Alpine": make+busybox son ya de takana, auditados, y
el auto-alojamiento sigue siendo bit-a-bit.** Siguiente pieza de la variante (b): linux-headers →
bwrap → rust/llvm (cada una swapeada y re-verificada con este mismo comando).
@@ -487,5 +487,5 @@ de hardware que esta tarea traslada a un host capaz.
arje trae su propio empaquetador (`03_ukupacha/arje/init/arje-packager`):
`arje-packager --seed <CARD.json> --out <initramfs.cpio.gz> [--bin LABEL=PATH]…`. Útil para comparar
el initramfs **canónico de arje** contra el que sale del rootfs de hammer (§4). No reemplaza esta
validación: aquí probamos el rootfs que **hammer** sella, no el que arje arma.
el initramfs **canónico de arje** contra el que sale del rootfs de takana (§4). No reemplaza esta
validación: aquí probamos el rootfs que **takana** sella, no el que arje arma.