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:
+2
-2
@@ -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
@@ -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)).
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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>`
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
@@ -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 0–6:** montamos `hammer` **sobre Alpine** (musl + busybox + FHS mutable, ya cocidos).
|
||||
> **Fase 0–6:** 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
@@ -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 0–6 (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 0–6 (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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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 0–6 + bootstrap Stage 0–2 + 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
@@ -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 2–4 ✅. 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
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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).
|
||||
|
||||
@@ -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).
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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**:
|
||||
|
||||
|
||||
@@ -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
@@ -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) +
|
||||
|
||||
@@ -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
@@ -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 1–5 ✅ |
|
||||
| 0010 | Arranque por grafo: el menú de boot como navegación del grafo | aceptado; lado takana 1–5 ✅ |
|
||||
| 0011 | Etapa Escritorio: campaña KDE Plasma 6 | aceptado; gates H-Qt y H-QML pasados |
|
||||
| 0012 | El árbol de fuentes es caché y workspace a la vez | **PENDIENTE, sin decidir** |
|
||||
| 0013 | Mirror de fuentes: la URL es transporte, el `sha256` es la identidad | ACEPTADO, implementado |
|
||||
|
||||
---
|
||||
|
||||
## 17. Superficie del CLI (`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
@@ -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)
|
||||
|
||||
@@ -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`).
|
||||
|
||||
@@ -12,7 +12,7 @@ build determinista + mutable + diario + `.swm`.
|
||||
|
||||
## Decisión
|
||||
|
||||
Montar y validar toda la capa de `hammer` **sobre Alpine Linux** (Fases 0–6). Sólo después,
|
||||
Montar y validar toda la capa de `takana` **sobre Alpine Linux** (Fases 0–6). Sólo después,
|
||||
una vez probado el concepto, bajar a la distro propia (track posterior).
|
||||
|
||||
## Razones
|
||||
|
||||
@@ -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 0–6 sin esa complejidad.
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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ó.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 1–5 ✅). Instalador live EFI validado 2-stage en
|
||||
**Estado:** aceptado; lado takana implementado (pasos 1–5 ✅). 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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
@@ -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.
|
||||
|
||||
|
||||
@@ -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 —
|
||||
|
||||
|
@@ -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.
|
@@ -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)
|
||||
|
||||
@@ -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.**
|
||||
|
||||
|
||||
@@ -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`.
|
||||
|
||||
|
||||
@@ -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 1–2) 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→T1–T4 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
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||

|
||||
|
||||
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)
|
||||
|
||||
@@ -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" \
|
||||
|
||||

|
||||
|
||||
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
|
||||
|
||||
@@ -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**.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user