takana etapa 5d: comentarios de crates, CLAUDE.md y el skill de granja

205 lineas en 73 ficheros de crates, mas la prosa de CLAUDE.md y del skill,
que se me habian quedado afuera de los barridos anteriores (no eran ni recetas
ni docs/ ni scripts/).

EL BARRIDO ANCHO ESTUVO A UN COMMIT DE ROMPER EL CORPUS ENTERO.

El primer intento reescribia los .rs completos, no solo los comentarios. Entre
las lineas de codigo que tocaba estaban SIETE etiquetas de separacion de
dominio, que son ENTRADA DE HASH:

  b"hammer-tree-v1"        <- el prefijo de ArtifactHash::of_tree (hash.rs:60)
  b"hammer-seed-v1"           la funcion que hashea TODOS los artefactos:
  b"hammer-stage1-rootfs-v2"  cambiarla mueve los 4750 hashes del store
  b"hammer-product-rootfs-v3"
  b"hammer-product-attested-v2"
  b"hammer-builder-rootfs-v1"
  b"hammer-attest-dev-rootkey-0001!!"  <- clave raiz de atestacion, [u8;32]

Revertido y rehecho solo sobre comentarios, esquivando ademas las cadenas
crudas de Rust (r#"..."#) porque el SYSTEM_PROMPT del traductor tiene lineas
que empiezan como comentario.

Controles: las 7 etiquetas siguen ahi, el diff toca CERO lineas de codigo,
605 tests en verde y el hash de zlib sigue en b3:dc363f26.

La leccion es la misma de toda esta etapa: un literal que parece prosa puede
ser entrada de hash, y la unica forma de saberlo es mirar donde se usa.
This commit is contained in:
Sergio
2026-09-09 19:38:33 +00:00
parent 1b7e6f947b
commit b818f5249f
61 changed files with 211 additions and 211 deletions
+2 -2
View File
@@ -3,7 +3,7 @@ name: granja
description: Cómo se compila en este proyecto — dónde está el worker, cómo se enchufa, y por qué NO se levantan cajas de pago. Usar al empezar cualquier trabajo de build, cosecha o estado de la granja. description: Cómo se compila en este proyecto — dónde está el worker, cómo se enchufa, y por qué NO se levantan cajas de pago. Usar al empezar cualquier trabajo de build, cosecha o estado de la granja.
--- ---
# La granja de hammer # La granja de takana
## Lo primero, porque cuesta dinero equivocarse ## Lo primero, porque cuesta dinero equivocarse
@@ -32,7 +32,7 @@ worker vivo» aunque el worker esté compilando. Comprobar el fichero antes de c
## Reglas que no son estilo ## Reglas que no son estilo
1. **Todo `hammer build` bajo `flock -o work/.farm-build.lock`.** El `-o` no es opcional: sin él un 1. **Todo `takana build` bajo `flock -o work/.farm-build.lock`.** El `-o` no es opcional: sin él un
proceso fugado hereda el fd y deja la granja sin compilar, en silencio. Ver regla 1 de CLAUDE.md. proceso fugado hereda el fd y deja la granja sin compilar, en silencio. Ver regla 1 de CLAUDE.md.
2. **Un lock tomado sin build a la vista se diagnostica con `fuser -v work/.farm-build.lock`**, que 2. **Un lock tomado sin build a la vista se diagnostica con `fuser -v work/.farm-build.lock`**, que
nombra al fugado. Pasó dos veces el mismo día. nombra al fugado. Pasó dos veces el mismo día.
+4 -4
View File
@@ -4,7 +4,7 @@ Este repo lo trabajan **varios agentes a la vez** (hoy: frente granja/store y fr
aquí abajo no son preferencias de estilo: son las dos formas conocidas de que un agente destruya el aquí abajo no son preferencias de estilo: son las dos formas conocidas de que un agente destruya el
trabajo de otro sin enterarse. El resto del diseño está en `docs/`. trabajo de otro sin enterarse. El resto del diseño está en `docs/`.
## 1. Todo `hammer build` va envuelto en `flock` ## 1. Todo `takana build` va envuelto en `flock`
```sh ```sh
flock work/.farm-build.lock ./target/release/takana --store ./store build <receta> flock work/.farm-build.lock ./target/release/takana --store ./store build <receta>
@@ -16,7 +16,7 @@ Para una tanda, tomar el lock una sola vez y no por receta:
flock work/.farm-build.lock bash -c 'for r in ...; do ./target/release/takana --store ./store build "$r"; done' flock work/.farm-build.lock bash -c 'for r in ...; do ./target/release/takana --store ./store build "$r"; done'
``` ```
**Por qué.** `hammer build` comparte `work/sources/<dep>-<sha>` entre todas las recetas. Dos builds **Por qué.** `takana build` comparte `work/sources/<dep>-<sha>` entre todas las recetas. Dos builds
concurrentes que compartan una dependencia se pisan el árbol de fuentes: uno hace fetch y lo borra concurrentes que compartan una dependencia se pisan el árbol de fuentes: uno hace fetch y lo borra
mientras el otro lo usa, y **el árbol queda roto para siempre** — reintentar no lo arregla. Medido a 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 escala: al invalidar libdrm, ~93 de 205 recetas KDE murieron con `/src/.zwrap/cc is not a full path
@@ -43,8 +43,8 @@ el estilo `exec 9>` + `flock 9`, que es vulnerable igual** (haría falta `9>&-`
conocida, no barrida. conocida, no barrida.
`scripts/farm/farm-worker-loop.sh` y `campana-deuda.sh` ya toman **ese mismo fichero de lock**, así `scripts/farm/farm-worker-loop.sh` y `campana-deuda.sh` ya toman **ese mismo fichero de lock**, así
que usarlo nos serializa con la granja además de entre nosotros. **No está dentro de `hammer build` que usarlo nos serializa con la granja además de entre nosotros. **No está dentro de `takana build`
a propósito**: esos scripts lo toman por fuera y hammer se bloquearía contra ellos. a propósito**: esos scripts lo toman por fuera y takana se bloquearía contra ellos.
## 1 bis. El worker es el LXC PRESTADO, y es GRATIS: no se levanta nada en Hetzner ## 1 bis. El worker es el LXC PRESTADO, y es GRATIS: no se levanta nada en Hetzner
+1 -1
View File
@@ -5,7 +5,7 @@ edition.workspace = true
license.workspace = true license.workspace = true
authors.workspace = true authors.workspace = true
repository.workspace = true repository.workspace = true
description = "Daemon de hammer: bus de agente (/run/agent.sock) y diario de mutaciones (fanotify)." description = "Daemon de takana: bus de agente (/run/agent.sock) y diario de mutaciones (fanotify)."
[[bin]] [[bin]]
name = "hammerd" name = "hammerd"
+4 -4
View File
@@ -1,14 +1,14 @@
//! Adaptador de transporte arje-bus → bus de agente de hammer (último tramo de B.2). //! Adaptador de transporte arje-bus → bus de agente de takana (último tramo de B.2).
//! //!
//! arje (el init/PID 1) supervisa cada Ente y, al morir, difunde `BusEvent` por su bus //! arje (el init/PID 1) supervisa cada Ente y, al morir, difunde `BusEvent` por su bus
//! (`arje-bus`: `BusRequest::Subscribe` + `BusPayload::Event`). Este módulo se **suscribe** a //! (`arje-bus`: `BusRequest::Subscribe` + `BusPayload::Event`). Este módulo se **suscribe** a
//! ese stream y traduce cada evento al vocabulario de hammer (`crashes::Lifecycle` → //! ese stream y traduce cada evento al vocabulario de takana (`crashes::Lifecycle` →
//! `Event::Crashed`), publicándolo en el [`EventBus`] → `/run/agent.sock` → la capa de IA. //! `Event::Crashed`), publicándolo en el [`EventBus`] → `/run/agent.sock` → la capa de IA.
//! //!
//! ## Por qué un mirror y no una dependencia de `arje-bus` //! ## Por qué un mirror y no una dependencia de `arje-bus`
//! //!
//! `arje-bus` arrastra el grafo de crates de arje (arje-card → card-core → …). Acoplarlo aquí //! `arje-bus` arrastra el grafo de crates de arje (arje-card → card-core → …). Acoplarlo aquí
//! rompería el build hermético/standalone de hammer. En su lugar **releemos el frame postcard**: //! rompería el build hermético/standalone de takana. En su lugar **releemos el frame postcard**:
//! el bus usa frames `u32-BE-len + postcard(BusMessage)`, y reproducimos sólo el subconjunto que //! el bus usa frames `u32-BE-len + postcard(BusMessage)`, y reproducimos sólo el subconjunto que
//! un suscriptor necesita. El layout está verificado byte-a-byte contra `arje-bus` real (mismas //! un suscriptor necesita. El layout está verificado byte-a-byte contra `arje-bus` real (mismas
//! versiones de `ulid` 1.2 y `postcard` 1.1; `ulid` serializa como string). Si arje reordena las //! versiones de `ulid` 1.2 y `postcard` 1.1; `ulid` serializa como string). Si arje reordena las
@@ -90,7 +90,7 @@ enum LifecycleStatus {
} }
impl BusEvent { impl BusEvent {
/// Traduce el evento de arje a la señal normalizada de hammer. El `label` del Ente es el /// Traduce el evento de arje a la señal normalizada de takana. El `label` del Ente es el
/// `service`; una muerte por señal usa la convención de shell `128 + signum` para que el /// `service`; una muerte por señal usa la convención de shell `128 + signum` para que el
/// código sea siempre ≠ 0. `None` = evento consumido sin señal (Parked/Refloored: son /// código sea siempre ≠ 0. `None` = evento consumido sin señal (Parked/Refloored: son
/// vaivén del piso gráfico, no ciclo de vida que le importe a la capa de IA). /// vaivén del piso gráfico, no ciclo de vida que le importe a la capa de IA).
+1 -1
View File
@@ -32,7 +32,7 @@ pub fn ensure_fifo(path: &Path) -> std::io::Result<PathBuf> {
Err(e) => return Err(e), Err(e) => return Err(e),
} }
// `nix::unistd::mkfifo` con permisos 0o660: owner + group escriben/leen, world nada. // `nix::unistd::mkfifo` con permisos 0o660: owner + group escriben/leen, world nada.
// El humano normalmente está en el grupo del daemon (p. ej. `hammer`); la IA agente // El humano normalmente está en el grupo del daemon (p. ej. `takana`); la IA agente
// entra por el socket. // entra por el socket.
use nix::sys::stat::Mode; use nix::sys::stat::Mode;
nix::unistd::mkfifo(path, Mode::S_IRUSR | Mode::S_IWUSR | Mode::S_IRGRP | Mode::S_IWGRP) nix::unistd::mkfifo(path, Mode::S_IRUSR | Mode::S_IWUSR | Mode::S_IRGRP | Mode::S_IWGRP)
+1 -1
View File
@@ -8,7 +8,7 @@
//! //!
//! Este módulo es el **sink** de ese flujo (B.2 "exponer el CRASHED a la capa de IA"): //! Este módulo es el **sink** de ese flujo (B.2 "exponer el CRASHED a la capa de IA"):
//! traduce la señal de ciclo de vida normalizada al vocabulario del bus de agente de //! traduce la señal de ciclo de vida normalizada al vocabulario del bus de agente de
//! hammer (`Event::Crashed`) y la bombea al [`EventBus`] → toda conexión del //! takana (`Event::Crashed`) y la bombea al [`EventBus`] → toda conexión del
//! `/run/agent.sock` (la capa de IA) la recibe. //! `/run/agent.sock` (la capa de IA) la recibe.
//! //!
//! ## Frontera de acoplamiento //! ## Frontera de acoplamiento
+4 -4
View File
@@ -1,5 +1,5 @@
//! `hammerd` — daemon de hammer. Tres responsabilidades: //! `hammerd` — daemon de takana. Tres responsabilidades:
//! 1. Diario de mutaciones: fanotify sobre /bin,/sbin,/lib,/etc → `hammer-journal`. //! 1. Diario de mutaciones: fanotify sobre /bin,/sbin,/lib,/etc → `takana-journal`.
//! 2. FIFO de control humano: `/run/init.control` (texto crudo, una línea por comando). //! 2. FIFO de control humano: `/run/init.control` (texto crudo, una línea por comando).
//! 3. Bus de agente: `/run/agent.sock` (JSON-líneas, SO_PEERCRED). Ver `docs/07-agent-bus.md`. //! 3. Bus de agente: `/run/agent.sock` (JSON-líneas, SO_PEERCRED). Ver `docs/07-agent-bus.md`.
//! //!
@@ -20,7 +20,7 @@ mod events;
mod watcher; mod watcher;
#[derive(Parser)] #[derive(Parser)]
#[command(name = "hammerd", version, about = "Daemon de hammer: bus de agente + diario.")] #[command(name = "hammerd", version, about = "Daemon de takana: bus de agente + diario.")]
struct Args { struct Args {
/// Socket del bus de agente. /// Socket del bus de agente.
#[arg(long, default_value = "/run/agent.sock")] #[arg(long, default_value = "/run/agent.sock")]
@@ -32,7 +32,7 @@ struct Args {
#[arg(long, default_value = "/var/lib/hammer/journal")] #[arg(long, default_value = "/var/lib/hammer/journal")]
journal: String, journal: String,
/// Directorio raíz de overlays (para que el watcher pueda filtrar eventos bajo overlays /// Directorio raíz de overlays (para que el watcher pueda filtrar eventos bajo overlays
/// activos). Default coincide con `hammer try`. /// activos). Default coincide con `takana try`.
#[arg(long, default_value = takana_overlay::DEFAULT_STATE_ROOT)] #[arg(long, default_value = takana_overlay::DEFAULT_STATE_ROOT)]
overlay_state_root: String, overlay_state_root: String,
/// Directorios extra a vigilar; si vacío, usa los defaults del FHS (SDD 05 §2). /// Directorios extra a vigilar; si vacío, usa los defaults del FHS (SDD 05 §2).
+2 -2
View File
@@ -11,7 +11,7 @@
//! * Cada evento se materializa como `MutationEvent` con `source = External` y la `op` //! * Cada evento se materializa como `MutationEvent` con `source = External` y la `op`
//! se deriva (`classify_op`): si el fd resuelve a un path borrado (`" (deleted)"`) es //! se deriva (`classify_op`): si el fd resuelve a un path borrado (`" (deleted)"`) es
//! `Delete`; si el diario ya conocía ese path (con un evento previo no-`Delete`) es //! `Delete`; si el diario ya conocía ese path (con un evento previo no-`Delete`) es
//! `Edit`; en otro caso es `Create` (primera mutación que hammer observa sobre el path). //! `Edit`; en otro caso es `Create` (primera mutación que takana observa sobre el path).
//! Nota: `FAN_CLOSE_WRITE` no distingue un `rename`-sobre-destino de una reescritura in //! Nota: `FAN_CLOSE_WRITE` no distingue un `rename`-sobre-destino de una reescritura in
//! situ, ni reporta el nombre en eventos de directorio — la atribución plena de //! situ, ni reporta el nombre en eventos de directorio — la atribución plena de
//! `Create`/`Move`/`Delete` vía `FAN_REPORT_DFID_NAME` (con `open_by_handle_at` y //! `Create`/`Move`/`Delete` vía `FAN_REPORT_DFID_NAME` (con `open_by_handle_at` y
@@ -203,7 +203,7 @@ impl Watcher {
/// * `deleted` (el fd resuelve a `" (deleted)"`) ⇒ `Delete`. /// * `deleted` (el fd resuelve a `" (deleted)"`) ⇒ `Delete`.
/// * el diario ya tiene un evento previo no-`Delete` para el path ⇒ `Edit` (el archivo /// * el diario ya tiene un evento previo no-`Delete` para el path ⇒ `Edit` (el archivo
/// ya era conocido y se reescribió). /// ya era conocido y se reescribió).
/// * en otro caso ⇒ `Create` — la primera mutación que hammer observa sobre el path, o /// * en otro caso ⇒ `Create` — la primera mutación que takana observa sobre el path, o
/// una resurrección tras un `Delete` previo. /// una resurrección tras un `Delete` previo.
/// ///
/// No es una verdad absoluta del FS (un archivo preexistente al daemon, editado por /// No es una verdad absoluta del FS (un archivo preexistente al daemon, editado por
+1 -1
View File
@@ -8,7 +8,7 @@
//! capacidad devuelve `Error{code:"no_cap"}`, lo que prueba el dispatcher + el gate. //! capacidad devuelve `Error{code:"no_cap"}`, lo que prueba el dispatcher + el gate.
// El binario no expone una API pública; importamos sus módulos privados desde // El binario no expone una API pública; importamos sus módulos privados desde
// `path = ...`. `proto` se movió a hammer-core en Fase 6. // `path = ...`. `proto` se movió a takana-core en Fase 6.
#[path = "../src/events.rs"] #[path = "../src/events.rs"]
mod events; mod events;
#[path = "../src/control.rs"] #[path = "../src/control.rs"]
+2 -2
View File
@@ -1,9 +1,9 @@
//! netup — configuración de red mínima Rust-nativa para el userland de hammer (Etapa C). //! netup — configuración de red mínima Rust-nativa para el userland de takana (Etapa C).
//! //!
//! Reemplaza el `ip` de busybox + la IP estática hardcodeada del driver de rebuild por un binario //! Reemplaza el `ip` de busybox + la IP estática hardcodeada del driver de rebuild por un binario
//! propio: levanta la interfaz, negocia un lease DHCPv4 y aplica IP/ruta/DNS vía netlink — todo //! propio: levanta la interfaz, negocia un lease DHCPv4 y aplica IP/ruta/DNS vía netlink — todo
//! síncrono, sin tokio, dependiendo sólo de libc (ABI estable del kernel). Como hammerd/arje, es //! síncrono, sin tokio, dependiendo sólo de libc (ABI estable del kernel). Como hammerd/arje, es
//! código hammer-propio: no hay un cliente DHCP Rust "maduro" para adoptar al estilo ripgrep. //! código takana-propio: no hay un cliente DHCP Rust "maduro" para adoptar al estilo ripgrep.
//! //!
//! Uso: `netup [interfaz]` (sin argumento, autodetecta la primera interfaz no-loopback). //! Uso: `netup [interfaz]` (sin argumento, autodetecta la primera interfaz no-loopback).
+9 -9
View File
@@ -8,12 +8,12 @@
//! base → swm.verify_base(local) → debe ser Ok //! base → swm.verify_base(local) → debe ser Ok
//! build → para cada source_patch: AgentClient.compile() vía el bus (o saltar si --no-bus) //! build → para cada source_patch: AgentClient.compile() vía el bus (o saltar si --no-bus)
//! try → takana_overlay::try_overlay() (o usar --prefix) //! try → takana_overlay::try_overlay() (o usar --prefix)
//! apply → hammer-core::apply para config_edit/file_drop + hydrate para source_patch //! apply → takana-core::apply para config_edit/file_drop + hydrate para source_patch
//! verify → spot-checks post-condición sobre disco //! verify → spot-checks post-condición sobre disco
//! propose → devuelve Proposal con overlay_id + log de checks //! propose → devuelve Proposal con overlay_id + log de checks
//! ``` //! ```
//! //!
//! La IA NUNCA ejecuta `hammer commit`. El humano lo hace tras revisar el `Proposal`. //! La IA NUNCA ejecuta `takana commit`. El humano lo hace tras revisar el `Proposal`.
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::time::Duration; use std::time::Duration;
@@ -101,7 +101,7 @@ impl<T: IntentTranslator> Orchestrator<T> {
}; };
// 5. APPLY — recorremos las mutaciones en orden. En source_patch, dispatch al bus // 5. APPLY — recorremos las mutaciones en orden. En source_patch, dispatch al bus
// o skip; en config_edit/file_drop, primitivas de hammer-core::apply. // o skip; en config_edit/file_drop, primitivas de takana-core::apply.
let mut applied = AppliedCounts::default(); let mut applied = AppliedCounts::default();
let mut checks: Vec<VerifyCheck> = Vec::new(); let mut checks: Vec<VerifyCheck> = Vec::new();
let mut skipped_source_patches = 0usize; let mut skipped_source_patches = 0usize;
@@ -356,10 +356,10 @@ fn wait_for_crash(policy: &RepairPolicy) -> Option<CrashInfo> {
None None
} }
/// Stub: `hammer-build` no es dependencia directa de `hammer-agent` para no arrastrar el /// Stub: `takana-build` no es dependencia directa de `takana-agent` para no arrastrar el
/// lab a clientes que sólo quieran hablar con el bus. Implementamos la hidratación local /// lab a clientes que sólo quieran hablar con el bus. Implementamos la hidratación local
/// con `std::fs::hard_link` directamente porque es trivial. Si el árbol del artefacto crece /// con `std::fs::hard_link` directamente porque es trivial. Si el árbol del artefacto crece
/// (symlinks, perms más finos), promovemos a `hammer-build::run_hydrate` añadiendo la dep /// (symlinks, perms más finos), promovemos a `takana-build::run_hydrate` añadiendo la dep
/// de manera condicional. /// de manera condicional.
fn hammer_build_run_hydrate(artifact_dir: &Path, target_fhs: &Path) -> Result<usize> { fn hammer_build_run_hydrate(artifact_dir: &Path, target_fhs: &Path) -> Result<usize> {
let mut files = 0usize; let mut files = 0usize;
@@ -383,7 +383,7 @@ fn hammer_build_run_hydrate(artifact_dir: &Path, target_fhs: &Path) -> Result<us
} }
let _ = std::fs::remove_file(&dst); let _ = std::fs::remove_file(&dst);
std::fs::hard_link(&src, &dst).or_else(|_| { std::fs::hard_link(&src, &dst).or_else(|_| {
// Cross-fs ⇒ caemos a copia. No optimizamos: hammer-agent espera // Cross-fs ⇒ caemos a copia. No optimizamos: takana-agent espera
// mismo FS; sólo damos el fallback como cinturón de seguridad. // mismo FS; sólo damos el fallback como cinturón de seguridad.
std::fs::copy(&src, &dst).map(|_| ()) std::fs::copy(&src, &dst).map(|_| ())
})?; })?;
@@ -470,8 +470,8 @@ impl VerifyCheck {
} }
} }
/// El resultado del bucle. El humano lo lee para decidir `hammer commit <overlay_id>` (si /// El resultado del bucle. El humano lo lee para decidir `takana commit <overlay_id>` (si
/// está en modo Overlay) o `hammer discard`. /// está en modo Overlay) o `takana discard`.
#[derive(Debug, Clone, serde::Serialize)] #[derive(Debug, Clone, serde::Serialize)]
pub struct Proposal { pub struct Proposal {
pub intent: String, pub intent: String,
@@ -496,7 +496,7 @@ pub struct RepairAttempt {
pub attempt: u32, pub attempt: u32,
pub intent: String, pub intent: String,
/// `Some` cuando el ciclo abrió un overlay; preservado para que el humano pueda hacer /// `Some` cuando el ciclo abrió un overlay; preservado para que el humano pueda hacer
/// `hammer discard <id>` sobre intentos intermedios si lo desea. /// `takana discard <id>` sobre intentos intermedios si lo desea.
pub overlay_id: Option<String>, pub overlay_id: Option<String>,
/// Si tras este intento llegó un `Crashed`, el servicio y código. `None` ⇒ estabilizado. /// Si tras este intento llegó un `Crashed`, el servicio y código. `None` ⇒ estabilizado.
pub triggered_crash: Option<CrashInfo>, pub triggered_crash: Option<CrashInfo>,
+1 -1
View File
@@ -5,7 +5,7 @@ edition.workspace = true
license.workspace = true license.workspace = true
authors.workspace = true authors.workspace = true
repository.workspace = true repository.workspace = true
description = "Bootstrap from-scratch de hammer: Stage 0/1/2 hacia el auto-alojamiento (SDD 11)." description = "Bootstrap from-scratch de takana: Stage 0/1/2 hacia el auto-alojamiento (SDD 11)."
[dependencies] [dependencies]
takana-core.workspace = true takana-core.workspace = true
+36 -36
View File
@@ -1,4 +1,4 @@
//! Bootstrap from-scratch de hammer — el track posterior del [SDD 11](../../docs/11-bootstrap.md). //! Bootstrap from-scratch de takana — el track posterior del [SDD 11](../../docs/11-bootstrap.md).
//! //!
//! Esta primera entrega cubre el **Stage 0**: ingerir un toolchain semilla ya construido //! Esta primera entrega cubre el **Stage 0**: ingerir un toolchain semilla ya construido
//! (no se compila; ver [ADR 0008](../../docs/adr/0008-bootstrap-stages.md)) como una *fuente //! (no se compila; ver [ADR 0008](../../docs/adr/0008-bootstrap-stages.md)) como una *fuente
@@ -175,7 +175,7 @@ const STAGE1_COMPONENTS: &[&str] = &["musl", "busybox", "hammerd", "arje-zero"];
/// un `Card` `Virtual` cuyos `genesis` son `hammerd` (servicio `Native` con `Restart` — el /// un `Card` `Virtual` cuyos `genesis` son `hammerd` (servicio `Native` con `Restart` — el
/// `CRASHED` real) y una `console-getty` supervisada. La forma replica una seed real de arje /// `CRASHED` real) y una `console-getty` supervisada. La forma replica una seed real de arje
/// (`seeds/arje-qemu.card.json`) para que `Card::validate()` la acepte; los ULID son fijos para que /// (`seeds/arje-qemu.card.json`) para que `Card::validate()` la acepte; los ULID son fijos para que
/// el `RootfsHash` sea reproducible. Template autocontenido (opción A del SDD): hammer no depende de /// el `RootfsHash` sea reproducible. Template autocontenido (opción A del SDD): takana no depende de
/// `card-core` como librería; el pin a arje-zero + el boot en VM cubren el drift de schema. /// `card-core` como librería; el pin a arje-zero + el boot en VM cubren el drift de schema.
const STAGE1_SEED_CARD: &str = r#"{ const STAGE1_SEED_CARD: &str = r#"{
"schema_version": 1, "schema_version": 1,
@@ -447,13 +447,13 @@ const SSHD_CONFIG: &str = "Port 22\nListenAddress 0.0.0.0\nPermitRootLogin prohi
// ── Atestación de integridad al arranque (I4 / Etapa D, mitad de HAMMER) ───────────────────────── // ── Atestación de integridad al arranque (I4 / Etapa D, mitad de HAMMER) ─────────────────────────
// //
// Por la regla de oro del [plan arje↔hammer] (`PLAN-ATESTACION-Y-HAMMER.md` §B.1): hammer es dueño del // Por la regla de oro del [plan arje↔takana] (`PLAN-ATESTACION-Y-HAMMER.md` §B.1): takana es dueño del
// **`expected_hash` + firma + TrustStore**; arje es dueño del **gate al boot** (computa `blake3` de cada // **`expected_hash` + firma + TrustStore**; arje es dueño del **gate al boot** (computa `blake3` de cada
// binario crítico ANTES de incarnar y, si no casa, `halt`). Esta es la mitad de hammer: PRODUCIR el // binario crítico ANTES de incarnar y, si no casa, `halt`). Esta es la mitad de takana: PRODUCIR el
// manifiesto de hashes esperados (`/ente/attest.json`) que el gate A2 de arje verifica — "el // manifiesto de hashes esperados (`/ente/attest.json`) que el gate A2 de arje verifica — "el
// `expected_hash` de un `.swm` ES el BLAKE3 que arje atesta". El hash es `blake3` CRUDO del fichero // `expected_hash` de un `.swm` ES el BLAKE3 que arje atesta". El hash es `blake3` CRUDO del fichero
// (`ArtifactHash::of_file`), el mismo que computa `arje-cas::blake3_of`. La firma con la rootkey del // (`ArtifactHash::of_file`), el mismo que computa `arje-cas::blake3_of`. La firma con la rootkey del
// seed (A1, `arje-packager`) es una capa posterior; el manifiesto ya es verificable hoy por `hammer // seed (A1, `arje-packager`) es una capa posterior; el manifiesto ya es verificable hoy por `takana
// attest` (auto-validación: "reproducir, no confiar"). Es un fichero APARTE de la seed card ⇒ no toca // attest` (auto-validación: "reproducir, no confiar"). Es un fichero APARTE de la seed card ⇒ no toca
// el schema de `card-core` y el producto bootea igual con el arje-zero actual (que aún ignora A2). // el schema de `card-core` y el producto bootea igual con el arje-zero actual (que aún ignora A2).
@@ -475,7 +475,7 @@ pub struct AttestEntry {
pub b3: String, pub b3: String,
} }
/// Manifiesto de atestación que hammer emite en `/ente/attest.json`. `algo` fijo a `blake3` (crudo). /// Manifiesto de atestación que takana emite en `/ente/attest.json`. `algo` fijo a `blake3` (crudo).
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct AttestManifest { pub struct AttestManifest {
pub schema_version: u32, pub schema_version: u32,
@@ -514,7 +514,7 @@ fn build_attestation(root: &Path) -> Result<AttestManifest> {
/// Verifica un árbol FHS (`rootfs_dir`, p.ej. un product-rootfs hidratado) contra su propio /// Verifica un árbol FHS (`rootfs_dir`, p.ej. un product-rootfs hidratado) contra su propio
/// `/ente/attest.json`: recomputa el BLAKE3 de cada binario listado y lo compara. Es lo que el gate de /// `/ente/attest.json`: recomputa el BLAKE3 de cada binario listado y lo compara. Es lo que el gate de
/// arje hará al boot, hecho hoy por hammer (auto-validación de integridad del producto). /// arje hará al boot, hecho hoy por takana (auto-validación de integridad del producto).
pub fn verify_attestation(rootfs_dir: &Path) -> Result<AttestReport> { pub fn verify_attestation(rootfs_dir: &Path) -> Result<AttestReport> {
let manifest_path = rootfs_dir.join("ente/attest.json"); let manifest_path = rootfs_dir.join("ente/attest.json");
let raw = std::fs::read_to_string(&manifest_path).map_err(|e| { let raw = std::fs::read_to_string(&manifest_path).map_err(|e| {
@@ -541,7 +541,7 @@ pub fn verify_attestation(rootfs_dir: &Path) -> Result<AttestReport> {
} }
/// Seed de producto = la seed base con los cards de servicio APENDADOS al `genesis`. Composición vía /// Seed de producto = la seed base con los cards de servicio APENDADOS al `genesis`. Composición vía
/// `serde_json` (hammer sigue autocontenido: no depende de `card-core`). Determinista ⇒ el /// `serde_json` (takana sigue autocontenido: no depende de `card-core`). Determinista ⇒ el
/// `product-rootfs` es reproducible. El núcleo (hammerd + getty) se preserva tal cual. /// `product-rootfs` es reproducible. El núcleo (hammerd + getty) se preserva tal cual.
fn product_seed_card() -> Result<String> { fn product_seed_card() -> Result<String> {
let mut seed: serde_json::Value = serde_json::from_str(STAGE1_SEED_CARD) let mut seed: serde_json::Value = serde_json::from_str(STAGE1_SEED_CARD)
@@ -773,7 +773,7 @@ fn seal_product_rootfs(
/// Reproduce un conjunto de componentes DESDE UN REPO FIRMADO, devolviendo sus `(nombre,hash)` sellados /// Reproduce un conjunto de componentes DESDE UN REPO FIRMADO, devolviendo sus `(nombre,hash)` sellados
/// — el MISMO shape que [`build_components`], así [`assemble_product_rootfs`] los hidrata igual. Por cada /// — el MISMO shape que [`build_components`], así [`assemble_product_rootfs`] los hidrata igual. Por cada
/// componente: resuelve su cierre de deps del índice, puebla el catálogo con los `{dep}.toml` (para que /// componente: resuelve su cierre de deps del índice, puebla el catálogo con los `{dep}.toml` (para que
/// el lab materialice build-deps al reproducir, igual que `hammer install`), y REPRODUCE su `source_patch` /// el lab materialice build-deps al reproducir, igual que `takana install`), y REPRODUCE su `source_patch`
/// con `build_source_patch` (que sella y CHEQUEA el `expected_hash` anclado en el índice). Verifica la /// con `build_source_patch` (que sella y CHEQUEA el `expected_hash` anclado en el índice). Verifica la
/// firma del release ANTES de tocar nada: el producto NO se ensambla desde un repo no confiado. /// firma del release ANTES de tocar nada: el producto NO se ensambla desde un repo no confiado.
pub fn install_components_from_repo( pub fn install_components_from_repo(
@@ -844,7 +844,7 @@ fn load_swm_file(path: &Path) -> Result<takana_core::Swm> {
.map_err(|e| Error::Other(format!("no pude parsear {}: {e}", path.display()))) .map_err(|e| Error::Other(format!("no pude parsear {}: {e}", path.display())))
} }
/// Primera mutación `SourcePatch` de un `.swm` (un paquete hammer es un único source_patch). /// Primera mutación `SourcePatch` de un `.swm` (un paquete takana es un único source_patch).
fn source_patch_of(swm: &takana_core::Swm) -> Option<&takana_core::Mutation> { fn source_patch_of(swm: &takana_core::Swm) -> Option<&takana_core::Mutation> {
swm.mutations swm.mutations
.iter() .iter()
@@ -854,18 +854,18 @@ fn source_patch_of(swm: &takana_core::Swm) -> Option<&takana_core::Mutation> {
// ── Producto ATESTADO: la capa FIRMADA que el gate de arje verifica al boot (I4 / Etapa D) ─────── // ── Producto ATESTADO: la capa FIRMADA que el gate de arje verifica al boot (I4 / Etapa D) ───────
// //
// `product()` sella el `product-rootfs` con el arje-zero del núcleo + un `/ente/attest.json` de // `product()` sella el `product-rootfs` con el arje-zero del núcleo + un `/ente/attest.json` de
// AUTO-verificación (esquema paralelo que `hammer attest` valida, pero que el gate de arje NO lee). // AUTO-verificación (esquema paralelo que `takana attest` valida, pero que el gate de arje NO lee).
// El producto ATESTADO cierra el lazo canónico: hidrata el arje-zero CON gate (`arje-zero-attest`), // El producto ATESTADO cierra el lazo canónico: hidrata el arje-zero CON gate (`arje-zero-attest`),
// FIRMA la seed con `arje-packager` (una concesión Ed25519 por binario crítico, sobre su BLAKE3, bajo // FIRMA la seed con `arje-packager` (una concesión Ed25519 por binario crítico, sobre su BLAKE3, bajo
// una rootkey) y la sella como `product-attested-rootfs`. Booteado, el gate (política `halt`) recomputa // una rootkey) y la sella como `product-attested-rootfs`. Booteado, el gate (política `halt`) recomputa
// el BLAKE3 de cada exec crítico y aborta antes de incarnar si algo no casa la concesión firmada. // el BLAKE3 de cada exec crítico y aborta antes de incarnar si algo no casa la concesión firmada.
// //
// Esto reemplaza el spike `scripts/attest-boot-test.sh` (que overlayeaba a mano): ahora hammer-bootstrap // Esto reemplaza el spike `scripts/attest-boot-test.sh` (que overlayeaba a mano): ahora takana-bootstrap
// produce el producto atestado de forma estructurada. Reusa la cripto canónica (arje-attest/agora vía el // produce el producto atestado de forma estructurada. Reusa la cripto canónica (arje-attest/agora vía el
// packager) — cero criptografía nueva en hammer. La rootkey por defecto es determinista ⇒ las firmas // packager) — cero criptografía nueva en takana. La rootkey por defecto es determinista ⇒ las firmas
// Ed25519 (RFC 8032, nonce derivado del mensaje) son reproducibles bit a bit. // Ed25519 (RFC 8032, nonce derivado del mensaje) son reproducibles bit a bit.
/// Rootkey de desarrollo (32 bytes) con la que hammer firma la atestación por defecto. Determinista ⇒ /// Rootkey de desarrollo (32 bytes) con la que takana firma la atestación por defecto. Determinista ⇒
/// firmas reproducibles ⇒ `product-attested-rootfs` reproducible. El endurecimiento soberano (rootkey /// firmas reproducibles ⇒ `product-attested-rootfs` reproducible. El endurecimiento soberano (rootkey
/// fuera del árbol, `/etc/arje/rootkey.pub` / `ARJE_ATTEST_ROOTKEY_FILE`) es una capa posterior /// fuera del árbol, `/etc/arje/rootkey.pub` / `ARJE_ATTEST_ROOTKEY_FILE`) es una capa posterior
/// (roadmap Etapa D, hardening #3); por ahora se inyecta como override de [`AttestConfig::rootkey`]. /// (roadmap Etapa D, hardening #3); por ahora se inyecta como override de [`AttestConfig::rootkey`].
@@ -945,7 +945,7 @@ fn critical_bins_from_seed(seed: &serde_json::Value, staging: &Path) -> Vec<(Str
/// Ensambla el producto ATESTADO en `staging`: hidrata el `product-rootfs` base, PISA `/usr/bin/arje-zero` /// Ensambla el producto ATESTADO en `staging`: hidrata el `product-rootfs` base, PISA `/usr/bin/arje-zero`
/// con el init CON gate, fija `attest_policy` en la seed, la FIRMA con el packager (una concesión por /// con el init CON gate, fija `attest_policy` en la seed, la FIRMA con el packager (una concesión por
/// binario crítico sobre su BLAKE3), ancla la pubkey en `/etc/arje/rootkey.pub` (ancla soberana externa, /// binario crítico sobre su BLAKE3), ancla la pubkey en `/etc/arje/rootkey.pub` (ancla soberana externa,
/// hardening #3) y regenera `/ente/attest.json` (la auto-verificación de hammer, ahora coherente con el /// hardening #3) y regenera `/ente/attest.json` (la auto-verificación de takana, ahora coherente con el
/// arje-zero nuevo). El packager corre en el host (estático musl, sin loader, como busybox/bwrap). La /// arje-zero nuevo). El packager corre en el host (estático musl, sin loader, como busybox/bwrap). La
/// firma se valida E2E en QEMU (`scripts/attest-boot-test.sh`). /// firma se valida E2E en QEMU (`scripts/attest-boot-test.sh`).
fn assemble_attested_product_rootfs( fn assemble_attested_product_rootfs(
@@ -1030,7 +1030,7 @@ fn assemble_attested_product_rootfs(
// `attest_rootkey` declarada en el seed ⇒ un seed reescrito con OTRA rootkey cae en AutorNoConfiable // `attest_rootkey` declarada en el seed ⇒ un seed reescrito con OTRA rootkey cae en AutorNoConfiable
// (las concesiones no las firmó el ancla). Cierra el hueco que el WARN "sin ancla soberana externa" // (las concesiones no las firmó el ancla). Cierra el hueco que el WARN "sin ancla soberana externa"
// señala. La pubkey se LEE del seed ya firmado (`attest_rootkey`, lo que el packager computó de // señala. La pubkey se LEE del seed ya firmado (`attest_rootkey`, lo que el packager computó de
// nuestra rootkey privada) ⇒ cero criptografía nueva en hammer. // nuestra rootkey privada) ⇒ cero criptografía nueva en takana.
let signed_raw = std::fs::read_to_string(&seed_path) let signed_raw = std::fs::read_to_string(&seed_path)
.map_err(|e| Error::Other(format!("releer seed firmado: {e}")))?; .map_err(|e| Error::Other(format!("releer seed firmado: {e}")))?;
let signed: serde_json::Value = serde_json::from_str(&signed_raw) let signed: serde_json::Value = serde_json::from_str(&signed_raw)
@@ -1050,7 +1050,7 @@ fn assemble_attested_product_rootfs(
std::fs::write(staging.join("etc/arje/rootkey.pub"), &pubkey)?; std::fs::write(staging.join("etc/arje/rootkey.pub"), &pubkey)?;
// 6) regenerar /ente/attest.json: el arje-zero cambió ⇒ su BLAKE3 también, así que el manifiesto de // 6) regenerar /ente/attest.json: el arje-zero cambió ⇒ su BLAKE3 también, así que el manifiesto de
// auto-verificación de hammer (`hammer attest`) debe casar el árbol nuevo. El producto base lo // auto-verificación de takana (`takana attest`) debe casar el árbol nuevo. El producto base lo
// trae hidratado (hardlink read-only): romperlo antes de reescribir. // trae hidratado (hardlink read-only): romperlo antes de reescribir.
let attest = build_attestation(staging)?; let attest = build_attestation(staging)?;
let attest_json = serde_json::to_string_pretty(&attest) let attest_json = serde_json::to_string_pretty(&attest)
@@ -1141,7 +1141,7 @@ pub fn product_attested(
// **sólo** las herramientas de Stage 1, y se compara el **content-hash** (`of_tree`, no el hash // **sólo** las herramientas de Stage 1, y se compara el **content-hash** (`of_tree`, no el hash
// input-addressed del store) de stage1 vs stage1'. Iguales ⇒ el sistema se compila a sí mismo bit // input-addressed del store) de stage1 vs stage1'. Iguales ⇒ el sistema se compila a sí mismo bit
// a bit (auto-alojado y reproducible). El rebuild **dentro del rootfs** (bwrap/chroot/VM) corre en // a bit (auto-alojado y reproducible). El rebuild **dentro del rootfs** (bwrap/chroot/VM) corre en
// la VM destino; lo que vive en hammer es la **referencia** (content-hash de stage1) y la // la VM destino; lo que vive en takana es la **referencia** (content-hash de stage1) y la
// comparación. Ver `docs/runbooks/stage1-vm-boot.md`. // comparación. Ver `docs/runbooks/stage1-vm-boot.md`.
/// Veredicto de la verificación de auto-alojamiento de Stage 2. /// Veredicto de la verificación de auto-alojamiento de Stage 2.
@@ -1264,12 +1264,12 @@ pub fn all(
// //
// El Stage 1 que booteamos es un *runtime* (musl+busybox+hammerd+arje-zero): no trae compilador, no // El Stage 1 que booteamos es un *runtime* (musl+busybox+hammerd+arje-zero): no trae compilador, no
// puede reconstruirse. El **builder rootfs** es Stage 1 **+ el toolchain adentro** — la imagen que, // puede reconstruirse. El **builder rootfs** es Stage 1 **+ el toolchain adentro** — la imagen que,
// booteada en la VM, corre `hammer bootstrap stage1` dentro de sí misma y produce `stage1'`. Compara // booteada en la VM, corre `takana bootstrap stage1` dentro de sí misma y produce `stage1'`. Compara
// su content-hash con la referencia anclada por `stage2`: iguales ⇒ auto-alojamiento bit a bit. // su content-hash con la referencia anclada por `stage2`: iguales ⇒ auto-alojamiento bit a bit.
// //
// Variante (a) **pragmática** (SDD 11 §7.2): el toolchain entra desde Alpine (no construido por // Variante (a) **pragmática** (SDD 11 §7.2): el toolchain entra desde Alpine (no construido por
// hammer). Demuestra el *mecanismo* y cierra la reproducibilidad end-to-end; no es aún el // takana). Demuestra el *mecanismo* y cierra la reproducibilidad end-to-end; no es aún el
// auto-alojamiento *puro* (variante b: el toolchain construido por hammer desde fuente, incremental). // auto-alojamiento *puro* (variante b: el toolchain construido por takana desde fuente, incremental).
// //
// El builder NO se sella en el store (su /toolchain Alpine no es content-addressed y abultaría): se // El builder NO se sella en el store (su /toolchain Alpine no es content-addressed y abultaría): se
// ensambla en un `out_dir` que se empaqueta como initramfs (ver runbook §8c). Su identidad sí es // ensambla en un `out_dir` que se empaqueta como initramfs (ver runbook §8c). Su identidad sí es
@@ -1284,7 +1284,7 @@ pub fn all(
/// El toolchain es Alpine **dinámico**: sus binarios (bwrap/git/curl/cargo/make) no corren desde el /// El toolchain es Alpine **dinámico**: sus binarios (bwrap/git/curl/cargo/make) no corren desde el
/// userland Stage 1, que es **estático** (no hay loader en `/lib`). El driver instala un *shim* del /// userland Stage 1, que es **estático** (no hay loader en `/lib`). El driver instala un *shim* del
/// loader musl (`cp` a `/lib`) + `LD_LIBRARY_PATH`/`PATH` al toolchain, y a partir de ahí esas /// loader musl (`cp` a `/lib`) + `LD_LIBRARY_PATH`/`PATH` al toolchain, y a partir de ahí esas
/// herramientas son ejecutables; el `hammer` estático vive en `/usr/bin/hammer`. El sandbox de build /// herramientas son ejecutables; el `takana` estático vive en `/usr/bin/hammer`. El sandbox de build
/// (bwrap) anida en `/toolchain`. (El shim sólo se usa para *invocar* el toolchain desde fuera del /// (bwrap) anida en `/toolchain`. (El shim sólo se usa para *invocar* el toolchain desde fuera del
/// sandbox: fetch git/tarball; dentro del sandbox el loader está en su sitio.) /// sandbox: fetch git/tarball; dentro del sandbox el loader está en su sitio.)
const REBUILD_DRIVER: &str = r#"#!/bin/sh const REBUILD_DRIVER: &str = r#"#!/bin/sh
@@ -1349,13 +1349,13 @@ pub struct BuilderSpec {
/// Stage 1 rootfs ya sellado (nombre store `stage1-rootfs`) que el builder extiende — la base /// Stage 1 rootfs ya sellado (nombre store `stage1-rootfs`) que el builder extiende — la base
/// runtime sobre la que se hidrata el toolchain. /// runtime sobre la que se hidrata el toolchain.
pub stage1_rootfs: RootfsHash, pub stage1_rootfs: RootfsHash,
/// Semilla zig sellada (Stage 0). Se replica en el `/store` del builder para que el `hammer` /// Semilla zig sellada (Stage 0). Se replica en el `/store` del builder para que el `takana`
/// de adentro resuelva el toolchain por hash, igual que afuera. /// de adentro resuelva el toolchain por hash, igual que afuera.
pub seed_hash: ArtifactHash, pub seed_hash: ArtifactHash,
pub seed_kind: SeedKind, pub seed_kind: SeedKind,
/// Recetas a embeber en `/etc/hammer/recipes` (las que `stage1` rehidrata el rebuild). /// Recetas a embeber en `/etc/hammer/recipes` (las que `stage1` rehidrata el rebuild).
pub recipes_dir: PathBuf, pub recipes_dir: PathBuf,
/// Binario `hammer` estático → `/usr/bin/hammer`. Variante (a): el del host; (b): el que hammer /// Binario `takana` estático → `/usr/bin/hammer`. Variante (a): el del host; (b): el que takana
/// construye desde fuente. /// construye desde fuente.
pub hammer_bin: PathBuf, pub hammer_bin: PathBuf,
/// Toolchain del sandbox de build → `/toolchain` (variante a: el rootfs Alpine de `.dev-fs`). /// Toolchain del sandbox de build → `/toolchain` (variante a: el rootfs Alpine de `.dev-fs`).
@@ -1369,20 +1369,20 @@ pub struct BuilderSpec {
/// Caché de fuentes (`work/repos`, `work/tarballs`) → `/work`, para un rebuild **offline** y /// Caché de fuentes (`work/repos`, `work/tarballs`) → `/work`, para un rebuild **offline** y
/// determinista en la VM. `None` ⇒ el rebuild fetchea por red (la seed card trae `networking: full`). /// determinista en la VM. `None` ⇒ el rebuild fetchea por red (la seed card trae `networking: full`).
pub work_cache: Option<PathBuf>, pub work_cache: Option<PathBuf>,
/// Piezas del toolchain **construidas por hammer desde fuente** (variante b, SDD 11 §7.2b) que se /// Piezas del toolchain **construidas por takana desde fuente** (variante b, SDD 11 §7.2b) que se
/// montan sobre el path Alpine en `/toolchain`. Vacío ⇒ variante (a) pura-Alpine. Cada swap entra /// montan sobre el path Alpine en `/toolchain`. Vacío ⇒ variante (a) pura-Alpine. Cada swap entra
/// al hash lógico del builder: la procedencia deja de ser "todo Alpine" y se vuelve auditable. /// al hash lógico del builder: la procedencia deja de ser "todo Alpine" y se vuelve auditable.
pub swaps: Vec<ToolchainSwap>, pub swaps: Vec<ToolchainSwap>,
} }
/// Una herramienta del toolchain reemplazada por su build hammer desde fuente (SDD 11 §7.2b). El /// Una herramienta del toolchain reemplazada por su build takana desde fuente (SDD 11 §7.2b). El
/// binario sellado se monta sobre `rel_path` dentro de `/toolchain`, pisando la versión Alpine; su /// binario sellado se monta sobre `rel_path` dentro de `/toolchain`, pisando la versión Alpine; su
/// hash sellado se ancla en el hash lógico del builder para que el swap sea reproducible y auditable. /// hash sellado se ancla en el hash lógico del builder para que el swap sea reproducible y auditable.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct ToolchainSwap { pub struct ToolchainSwap {
/// Nombre del componente/receta (p. ej. `make`); también el nombre store del artefacto sellado. /// Nombre del componente/receta (p. ej. `make`); también el nombre store del artefacto sellado.
pub name: String, pub name: String,
/// `of_tree` del build hammer (el artefacto sellado del que sale el binario). /// `of_tree` del build takana (el artefacto sellado del que sale el binario).
pub artifact: ArtifactHash, pub artifact: ArtifactHash,
/// Path relativo del binario dentro del artefacto **y** dentro de `/toolchain` (p. ej. /// Path relativo del binario dentro del artefacto **y** dentro de `/toolchain` (p. ej.
/// `usr/bin/make`). El mismo en origen y destino: el layout del artefacto imita al de Alpine. /// `usr/bin/make`). El mismo en origen y destino: el layout del artefacto imita al de Alpine.
@@ -1500,7 +1500,7 @@ fn link_or_copy_tree(src: &Path, dst: &Path, skip: Option<&Path>) -> Result<()>
/// Ensambla el árbol del builder en `staging` (la pieza testeable, sin VM ni Alpine real): hidrata el /// Ensambla el árbol del builder en `staging` (la pieza testeable, sin VM ni Alpine real): hidrata el
/// Stage 1 rootfs como base, monta el toolchain en `/toolchain`, replica la semilla en `/store`, /// Stage 1 rootfs como base, monta el toolchain en `/toolchain`, replica la semilla en `/store`,
/// instala `hammer` + recetas + el driver `rebuild-stage1` y, opcionalmente, la caché de fuentes. /// instala `takana` + recetas + el driver `rebuild-stage1` y, opcionalmente, la caché de fuentes.
fn assemble_builder(spec: &BuilderSpec, store: &Store, staging: &Path) -> Result<()> { fn assemble_builder(spec: &BuilderSpec, store: &Store, staging: &Path) -> Result<()> {
// 1) Base: el Stage 1 rootfs sellado (read-only en el store ⇒ hardlinks seguros). // 1) Base: el Stage 1 rootfs sellado (read-only en el store ⇒ hardlinks seguros).
let stage1_dir = store.path_of(&spec.stage1_rootfs, "stage1-rootfs"); let stage1_dir = store.path_of(&spec.stage1_rootfs, "stage1-rootfs");
@@ -1527,7 +1527,7 @@ fn assemble_builder(spec: &BuilderSpec, store: &Store, staging: &Path) -> Result
} }
link_or_copy_tree(&spec.toolchain_src, &staging.join("toolchain"), None)?; link_or_copy_tree(&spec.toolchain_src, &staging.join("toolchain"), None)?;
// 3b) Swaps de variante (b): piezas construidas por hammer desde fuente pisan su versión Alpine // 3b) Swaps de variante (b): piezas construidas por takana desde fuente pisan su versión Alpine
// en /toolchain. Un `rel_path` puede apuntar a un BINARIO (estático musl ⇒ corre sin el shim // en /toolchain. Un `rel_path` puede apuntar a un BINARIO (estático musl ⇒ corre sin el shim
// del loader; p.ej. make, busybox) o a un DIRECTORIO (un árbol de headers; p.ej. los // del loader; p.ej. make, busybox) o a un DIRECTORIO (un árbol de headers; p.ej. los
// `usr/include/{linux,asm,…}` de linux-headers). En el caso directorio se reemplaza el árbol // `usr/include/{linux,asm,…}` de linux-headers). En el caso directorio se reemplaza el árbol
@@ -1560,7 +1560,7 @@ fn assemble_builder(spec: &BuilderSpec, store: &Store, staging: &Path) -> Result
} }
} }
// 4) Semilla → /store/<hash>-seed-<kind>, tal cual está sellada afuera, para que el `hammer` de // 4) Semilla → /store/<hash>-seed-<kind>, tal cual está sellada afuera, para que el `takana` de
// adentro resuelva el toolchain por hash sin re-ingerirla. // adentro resuelva el toolchain por hash sin re-ingerirla.
let seed_name = format!("seed-{}", spec.seed_kind.as_str()); let seed_name = format!("seed-{}", spec.seed_kind.as_str());
let seed_dir = store.path_of(&spec.seed_hash, &seed_name); let seed_dir = store.path_of(&spec.seed_hash, &seed_name);
@@ -1577,7 +1577,7 @@ fn assemble_builder(spec: &BuilderSpec, store: &Store, staging: &Path) -> Result
None, None,
)?; )?;
// 5) Binario hammer → /usr/bin/hammer (+x). // 5) Binario takana → /usr/bin/hammer (+x).
let hammer_dst = staging.join("usr/bin/hammer"); let hammer_dst = staging.join("usr/bin/hammer");
if std::fs::hard_link(&spec.hammer_bin, &hammer_dst).is_err() { if std::fs::hard_link(&spec.hammer_bin, &hammer_dst).is_err() {
std::fs::copy(&spec.hammer_bin, &hammer_dst)?; std::fs::copy(&spec.hammer_bin, &hammer_dst)?;
@@ -2226,7 +2226,7 @@ mod tests {
#[test] #[test]
fn assemble_attested_swaps_gate_signs_seed_and_regenerates_attest() { fn assemble_attested_swaps_gate_signs_seed_and_regenerates_attest() {
// Hermético: un `arje-packager` SINTÉTICO (script python) simula la firma — añade `attest`/ // Hermético: un `arje-packager` SINTÉTICO (script python) simula la firma — añade `attest`/
// `attest_rootkey` al seed-out. Valida el CABLEADO de hammer (pisar el init, romper hardlinks, // `attest_rootkey` al seed-out. Valida el CABLEADO de takana (pisar el init, romper hardlinks,
// fijar attest_policy, derivar los --bin de la seed, regenerar /ente/attest.json), sin la cripto // fijar attest_policy, derivar los --bin de la seed, regenerar /ente/attest.json), sin la cripto
// real (eso lo cubre E2E `scripts/attest-boot-test.sh` en QEMU). // real (eso lo cubre E2E `scripts/attest-boot-test.sh` en QEMU).
use std::os::unix::fs::PermissionsExt; use std::os::unix::fs::PermissionsExt;
@@ -2440,7 +2440,7 @@ open(os.path.join(os.path.dirname(out),"..","..","bins-seen.txt"),"w").write("\n
std::fs::write(w.join("bin/zig"), b"#!/bin/sh\necho zig\n").unwrap(); std::fs::write(w.join("bin/zig"), b"#!/bin/sh\necho zig\n").unwrap();
}); });
// Recetas, binario hammer y toolchain (Alpine sintético), fuera del store. // Recetas, binario takana y toolchain (Alpine sintético), fuera del store.
let recipes = dir.join("recipes"); let recipes = dir.join("recipes");
std::fs::create_dir_all(&recipes).unwrap(); std::fs::create_dir_all(&recipes).unwrap();
std::fs::write(recipes.join("musl.toml"), b"name = 'musl'\n").unwrap(); std::fs::write(recipes.join("musl.toml"), b"name = 'musl'\n").unwrap();
@@ -2484,7 +2484,7 @@ open(os.path.join(os.path.dirname(out),"..","..","bins-seen.txt"),"w").write("\n
std::fs::read_link(out.join("sbin/init")).unwrap(), std::fs::read_link(out.join("sbin/init")).unwrap(),
PathBuf::from("/usr/bin/arje-zero"), PathBuf::from("/usr/bin/arje-zero"),
); );
// Toolchain en /toolchain, semilla en /store, hammer + recetas + driver. // Toolchain en /toolchain, semilla en /store, takana + recetas + driver.
assert!(out.join("toolchain/usr/bin/make").is_file(), "toolchain montado"); assert!(out.join("toolchain/usr/bin/make").is_file(), "toolchain montado");
assert!( assert!(
out.join("store").join(spec.seed_hash.store_dir_name("seed-zig")).join("bin/zig").is_file(), out.join("store").join(spec.seed_hash.store_dir_name("seed-zig")).join("bin/zig").is_file(),
@@ -2494,7 +2494,7 @@ open(os.path.join(os.path.dirname(out),"..","..","bins-seen.txt"),"w").write("\n
assert!(out.join("etc/hammer/recipes/musl.toml").is_file(), "recetas embebidas"); assert!(out.join("etc/hammer/recipes/musl.toml").is_file(), "recetas embebidas");
assert!(out.join("usr/bin/rebuild-stage1").is_file(), "driver de rebuild"); assert!(out.join("usr/bin/rebuild-stage1").is_file(), "driver de rebuild");
// El driver y hammer son ejecutables. // El driver y takana son ejecutables.
use std::os::unix::fs::PermissionsExt; use std::os::unix::fs::PermissionsExt;
let m = std::fs::metadata(out.join("usr/bin/rebuild-stage1")).unwrap(); let m = std::fs::metadata(out.join("usr/bin/rebuild-stage1")).unwrap();
assert_eq!(m.permissions().mode() & 0o111, 0o111, "driver +x"); assert_eq!(m.permissions().mode() & 0o111, 0o111, "driver +x");
@@ -2559,7 +2559,7 @@ open(os.path.join(os.path.dirname(out),"..","..","bins-seen.txt"),"w").write("\n
let out = tmp.path().join("builder-out"); let out = tmp.path().join("builder-out");
let report = builder_rootfs(&spec, &store, &out).unwrap(); let report = builder_rootfs(&spec, &store, &out).unwrap();
// El make de /toolchain es ahora el de hammer, no el de Alpine. // El make de /toolchain es ahora el de takana, no el de Alpine.
let got = std::fs::read(out.join("toolchain/usr/bin/make")).unwrap(); let got = std::fs::read(out.join("toolchain/usr/bin/make")).unwrap();
assert_eq!(got, b"\x7fELF-hammer-make-static", "el swap pisó el make de Alpine"); assert_eq!(got, b"\x7fELF-hammer-make-static", "el swap pisó el make de Alpine");
+1 -1
View File
@@ -5,7 +5,7 @@ edition.workspace = true
license.workspace = true license.workspace = true
authors.workspace = true authors.workspace = true
repository.workspace = true repository.workspace = true
description = "El laboratorio de hammer: sandbox de build (bubblewrap + zig cc) e hidratación." description = "El laboratorio de takana: sandbox de build (bubblewrap + zig cc) e hidratación."
[dependencies] [dependencies]
takana-core.workspace = true takana-core.workspace = true
+1 -1
View File
@@ -616,7 +616,7 @@ fn fetch_tarball(
/// ///
/// # Por qué NO avisa cuando cae al mirror /// # Por qué NO avisa cuando cae al mirror
/// ///
/// Tentación evidente y **equivocada**: si `hammer build` avisara en cada descarga, el aviso sería /// Tentación evidente y **equivocada**: si `takana build` avisara en cada descarga, el aviso sería
/// ruido en 561 recetas y nadie lo leería. Peor: haría creer que el bit-rot está vigilado *acá*, /// ruido en 561 recetas y nadie lo leería. Peor: haría creer que el bit-rot está vigilado *acá*,
/// cuando construir y vigilar upstream son dos trabajos distintos. La vigilancia vive en /// cuando construir y vigilar upstream son dos trabajos distintos. La vigilancia vive en
/// `scripts/fuentes/fuentes-vigia.sh`, la corre el latido y escribe `docs/state/fuentes-vigia.json`. /// `scripts/fuentes/fuentes-vigia.sh`, la corre el latido y escribe `docs/state/fuentes-vigia.json`.
+2 -2
View File
@@ -3,7 +3,7 @@
//! Estrategia primaria (`LinkMode::Static`): hardlink directo. Cero copia, cero RPATH que //! Estrategia primaria (`LinkMode::Static`): hardlink directo. Cero copia, cero RPATH que
//! reescribir. El FHS queda con archivos reales (no symlinks) compartiendo inode con el store. //! reescribir. El FHS queda con archivos reales (no symlinks) compartiendo inode con el store.
//! Pisar un archivo del FHS rompe el hardlink (CoW al kernel) sin tocar el artefacto del store //! Pisar un archivo del FHS rompe el hardlink (CoW al kernel) sin tocar el artefacto del store
//! — base de rollback (`hammer hydrate <hash>` lo restaura). //! — base de rollback (`takana hydrate <hash>` lo restaura).
//! //!
//! Estrategia secundaria (`LinkMode::Dynamic`): los ELF que necesitan normalización de //! Estrategia secundaria (`LinkMode::Dynamic`): los ELF que necesitan normalización de
//! interpreter/RPATH se **copian** (no se hardlinkean: `patchelf` reescribe el binario y un //! interpreter/RPATH se **copian** (no se hardlinkean: `patchelf` reescribe el binario y un
@@ -65,7 +65,7 @@ pub fn hydrate(
// `is_dir()` a secas acepta un directorio VACÍO: hydrate proyectaría 0 ficheros y devolvería // `is_dir()` a secas acepta un directorio VACÍO: hydrate proyectaría 0 ficheros y devolvería
// un HydrateReport de éxito. Es el mismo agujero que ya se tapó en `Store::has` (2026-08-10) y // un HydrateReport de éxito. Es el mismo agujero que ya se tapó en `Store::has` (2026-08-10) y
// en `Store::seal` (2026-08-29): un vacío no es un artefacto, es un nombre. Acá duele especial // en `Store::seal` (2026-08-29): un vacío no es un artefacto, es un nombre. Acá duele especial
// porque el escritorio se hidrata así (`hammer hydrate <hash> --into`), y un FHS proyectado // porque el escritorio se hidrata así (`takana hydrate <hash> --into`), y un FHS proyectado
// desde nada sale "OK" y falla mucho más tarde, sin rastro de cuál artefacto faltaba. // desde nada sale "OK" y falla mucho más tarde, sin rastro de cuál artefacto faltaba.
let vacio = std::fs::read_dir(artifact_dir) let vacio = std::fs::read_dir(artifact_dir)
.map(|mut e| e.next().is_none()) .map(|mut e| e.next().is_none())
+9 -9
View File
@@ -239,9 +239,9 @@ pub fn build(
if matches!(detect_build_system(&src_tree), BuildSys::Cargo) if matches!(detect_build_system(&src_tree), BuildSys::Cargo)
|| eff_recipe.source.cargo_vendor == Some(true) || eff_recipe.source.cargo_vendor == Some(true)
{ {
// El source se copia DENTRO del repo hammer (que es un workspace Cargo) ⇒ si el Cargo.toml // El source se copia DENTRO del repo takana (que es un workspace Cargo) ⇒ si el Cargo.toml
// de la fuente NO declara su propio `[workspace]`, cargo cree que pertenece al workspace de // de la fuente NO declara su propio `[workspace]`, cargo cree que pertenece al workspace de
// hammer y `cargo vendor` aborta. Inyectamos un `[workspace]` vacío para aislarla. SÓLO si // takana y `cargo vendor` aborta. Inyectamos un `[workspace]` vacío para aislarla. SÓLO si
// no lo tiene ya (las recetas del corpus que lo parchean a mano siguen funcionando sin // no lo tiene ya (las recetas del corpus que lo parchean a mano siguen funcionando sin
// duplicar la tabla). Esto desbloquea genéricamente los imports Rust (Etapa G) sin un patch // duplicar la tabla). Esto desbloquea genéricamente los imports Rust (Etapa G) sin un patch
// por receta. Ver memoria 'cargo-recipe crate suelto'. // por receta. Ver memoria 'cargo-recipe crate suelto'.
@@ -312,7 +312,7 @@ pub fn build(
env.push(("CBUILD".into(), SANDBOX_NATIVE_TARGET.to_string())); env.push(("CBUILD".into(), SANDBOX_NATIVE_TARGET.to_string()));
env.push(("CHOST".into(), SANDBOX_NATIVE_TARGET.to_string())); env.push(("CHOST".into(), SANDBOX_NATIVE_TARGET.to_string()));
// *-sys C-FFI (Etapa G): si la receta declara `openssl` como dep, apuntá `openssl-sys` a la // *-sys C-FFI (Etapa G): si la receta declara `openssl` como dep, apuntá `openssl-sys` a la
// openssl hammer-built materializada en /usr (estática, musl). Sin esto openssl-sys busca en el // openssl takana-built materializada en /usr (estática, musl). Sin esto openssl-sys busca en el
// host y aborta ("Could not find directory of OpenSSL installation"). Condicional al dep ⇒ no // host y aborta ("Could not find directory of OpenSSL installation"). Condicional al dep ⇒ no
// rompe recetas que usen openssl vendored. Destraba la frontera cargo-* (reqwest/git tools). // rompe recetas que usen openssl vendored. Destraba la frontera cargo-* (reqwest/git tools).
if recipe.deps.build.iter().any(|d| d == "openssl") { if recipe.deps.build.iter().any(|d| d == "openssl") {
@@ -401,7 +401,7 @@ pub fn build(
} }
// Sidecar de provenance: escribimos la receta serializada DENTRO de out_dir antes del // Sidecar de provenance: escribimos la receta serializada DENTRO de out_dir antes del
// seal, para que quede congelada en el árbol read-only del store. `hammer export` la // seal, para que quede congelada en el árbol read-only del store. `takana export` la
// lee de vuelta para emitir `source_patch` con la receta original en vez de un // lee de vuelta para emitir `source_patch` con la receta original en vez de un
// `file_drop` opaco. Ver `docs/04-overlay.md` §provenance (Fase 4). // `file_drop` opaco. Ver `docs/04-overlay.md` §provenance (Fase 4).
let sidecar_path = out_dir.join(takana_core::store::RECIPE_SIDECAR_REL); let sidecar_path = out_dir.join(takana_core::store::RECIPE_SIDECAR_REL);
@@ -494,7 +494,7 @@ pub fn run_evidence(
env: Vec::new(), env: Vec::new(),
}; };
// 3. Ejecutar cada check y evaluarlo (la comparación exit/hash es pura, vive en hammer-core). // 3. Ejecutar cada check y evaluarlo (la comparación exit/hash es pura, vive en takana-core).
let mut results = Vec::with_capacity(recipe.evidence.checks.len()); let mut results = Vec::with_capacity(recipe.evidence.checks.len());
for c in &recipe.evidence.checks { for c in &recipe.evidence.checks {
tracing::info!(kind = c.kind.as_str(), cmd = %c.cmd, "evidence: check"); tracing::info!(kind = c.kind.as_str(), cmd = %c.cmd, "evidence: check");
@@ -612,7 +612,7 @@ fn inject_cargo_package_selector(recipe: &mut Recipe, src: &Path) {
} }
/// Garantiza que el `Cargo.toml` raíz de la fuente declare un `[workspace]` propio, para que cargo /// Garantiza que el `Cargo.toml` raíz de la fuente declare un `[workspace]` propio, para que cargo
/// NO la considere parte del workspace del repo hammer (que la contiene en `work/sources/`). Append /// NO la considere parte del workspace del repo takana (que la contiene en `work/sources/`). Append
/// idempotente: si ya hay `[workspace]` (crate suelto que es su propio workspace, o una receta que /// idempotente: si ya hay `[workspace]` (crate suelto que es su propio workspace, o una receta que
/// lo parchea), no toca nada. Caso virtual-manifest (root ya es `[workspace]`) tampoco se toca. /// lo parchea), no toca nada. Caso virtual-manifest (root ya es `[workspace]`) tampoco se toca.
fn ensure_cargo_workspace_isolation(src: &Path) -> takana_core::Result<()> { fn ensure_cargo_workspace_isolation(src: &Path) -> takana_core::Result<()> {
@@ -856,7 +856,7 @@ fn resolve_phases(recipe: &Recipe, src: &Path) -> takana_core::Result<Phases> {
// Hermético y reproducible: `--offline` (deps vendoreadas en la fuente) + // Hermético y reproducible: `--offline` (deps vendoreadas en la fuente) +
// `--locked` (Cargo.lock fijo, coherente con ADR 0006). El link va por un // `--locked` (Cargo.lock fijo, coherente con ADR 0006). El link va por un
// wrapper `zig cc` — cargo no acepta un linker de dos palabras, y zig provee // wrapper `zig cc` — cargo no acepta un linker de dos palabras, y zig provee
// el `libgcc_s` que el link dinámico musl pide (ver M2 del plan arje↔hammer). // el `libgcc_s` que el link dinámico musl pide (ver M2 del plan arje↔takana).
if recipe.build.target == SANDBOX_NATIVE_TARGET { if recipe.build.target == SANDBOX_NATIVE_TARGET {
// NATIVO: el sandbox del lab YA es x86_64 musl, así que no cruzamos. El rust del // NATIVO: el sandbox del lab YA es x86_64 musl, así que no cruzamos. El rust del
// sandbox sólo trae su std nativo (`x86_64-alpine-linux-musl`), no // sandbox sólo trae su std nativo (`x86_64-alpine-linux-musl`), no
@@ -894,7 +894,7 @@ fn resolve_phases(recipe: &Recipe, src: &Path) -> takana_core::Result<Phases> {
// sea un ERROR ("option `-C link-self-contained` is not supported on this // sea un ERROR ("option `-C link-self-contained` is not supported on this
// target") y NO usa self-contained — el baseline 9adefb82 linkea con zig sin // target") y NO usa self-contained — el baseline 9adefb82 linkea con zig sin
// chocar. Así que: añadir el flag SÓLO si el host del rustc NO es `-alpine-` // chocar. Así que: añadir el flag SÓLO si el host del rustc NO es `-alpine-`
// (i.e. un rust vanilla como el hammer-rust del frente self-host); para Alpine, // (i.e. un rust vanilla como el takana-rust del frente self-host); para Alpine,
// omitirlo. RUSTFLAGS alcanza a todas las unidades; los `bin_flags` (cargo rustc // omitirlo. RUSTFLAGS alcanza a todas las unidades; los `bin_flags` (cargo rustc
// --) sólo a la top. // --) sólo a la top.
// CC para los build-scripts (cc-rs compila el C de deps como libgit2-sys, // CC para los build-scripts (cc-rs compila el C de deps como libgit2-sys,
@@ -924,7 +924,7 @@ fn resolve_phases(recipe: &Recipe, src: &Path) -> takana_core::Result<Phases> {
// CROSS real (otro target que zig sí entiende): `--target` + linker por env // CROSS real (otro target que zig sí entiende): `--target` + linker por env
// per-triple. Hermético y reproducible: `--offline` (deps vendoreadas) + // per-triple. Hermético y reproducible: `--offline` (deps vendoreadas) +
// `--locked` (Cargo.lock fijo, ADR 0006). zig provee el `libgcc_s` que el link // `--locked` (Cargo.lock fijo, ADR 0006). zig provee el `libgcc_s` que el link
// dinámico musl pide (ver M2 del plan arje↔hammer). // dinámico musl pide (ver M2 del plan arje↔takana).
let setup = "printf '#!/bin/sh\\nexec zig cc -mcpu=baseline \"$@\"\\n' > \"$PWD/.hammer-zig-cc\" && \ let setup = "printf '#!/bin/sh\\nexec zig cc -mcpu=baseline \"$@\"\\n' > \"$PWD/.hammer-zig-cc\" && \
chmod +x \"$PWD/.hammer-zig-cc\" && "; chmod +x \"$PWD/.hammer-zig-cc\" && ";
let triple = rustc_triple(&recipe.build.target); let triple = rustc_triple(&recipe.build.target);
+5 -5
View File
@@ -39,7 +39,7 @@ impl std::fmt::Display for BuildFailure {
impl std::error::Error for BuildFailure {} impl std::error::Error for BuildFailure {}
impl BuildFailure { impl BuildFailure {
/// Recupera un `BuildFailure` de un error de hammer-core, si lo lleva dentro. El bus lo /// Recupera un `BuildFailure` de un error de takana-core, si lo lleva dentro. El bus lo
/// usa para separar `reason` de `log_tail`. /// usa para separar `reason` de `log_tail`.
pub fn from_error(e: &takana_core::Error) -> Option<&BuildFailure> { pub fn from_error(e: &takana_core::Error) -> Option<&BuildFailure> {
match e { match e {
@@ -457,7 +457,7 @@ impl Sandbox {
// default (16) el codegen paralelo de rustc reparte el crate en N unidades y la salida // default (16) el codegen paralelo de rustc reparte el crate en N unidades y la salida
// varía build-a-build incluso en la MISMA máquina y con entradas idénticas — Stage 2 lo // varía build-a-build incluso en la MISMA máquina y con entradas idénticas — Stage 2 lo
// cazó: `arje-zero` (cuyo workspace tawasuyu no fija perfil) divergía ~10 KB repartidos // cazó: `arje-zero` (cuyo workspace tawasuyu no fija perfil) divergía ~10 KB repartidos
// por .text/.rodata/.eh_frame, mientras `hammerd` (workspace hammer que SÍ pinea // por .text/.rodata/.eh_frame, mientras `hammerd` (workspace takana que SÍ pinea
// codegen-units=1) reproducía bit-a-bit. El lab lo impone en vez de confiar en que cada // codegen-units=1) reproducía bit-a-bit. El lab lo impone en vez de confiar en que cada
// repo upstream lo declare (SDD 09 §2). Inerte para builds no-Cargo (musl/busybox lo // repo upstream lo declare (SDD 09 §2). Inerte para builds no-Cargo (musl/busybox lo
// ignoran). Ver runbook §8c / SDD 11. // ignoran). Ver runbook §8c / SDD 11.
@@ -501,7 +501,7 @@ impl Sandbox {
/// El tee va SIEMPRE a stderr (tanto el stdout como el stderr del build): stdout del proceso /// El tee va SIEMPRE a stderr (tanto el stdout como el stderr del build): stdout del proceso
/// queda reservado para el resultado legible-por-máquina (el content-hash que imprime la CLI, /// queda reservado para el resultado legible-por-máquina (el content-hash que imprime la CLI,
/// p. ej. `bootstrap stage1`). Antes el stdout del build se teeaba al stdout del padre y se /// p. ej. `bootstrap stage1`). Antes el stdout del build se teeaba al stdout del padre y se
/// colaba en capturas tipo `PRIME=$(hammer … bootstrap stage1)`; con builds reales (no cacheados, /// colaba en capturas tipo `PRIME=$(takana … bootstrap stage1)`; con builds reales (no cacheados,
/// p. ej. el rebuild in-VM) eso son miles de líneas de `configure`/`make` ⇒ el argumento crece y /// p. ej. el rebuild in-VM) eso son miles de líneas de `configure`/`make` ⇒ el argumento crece y
/// la fase de verificación siguiente (`--rootfs "$PRIME"`) revienta el exec con E2BIG /// la fase de verificación siguiente (`--rootfs "$PRIME"`) revienta el exec con E2BIG
/// (Argument list too long). Va a stderr, junto a los logs de tracing. Ver runbook §8c. /// (Argument list too long). Va a stderr, junto a los logs de tracing. Ver runbook §8c.
@@ -555,7 +555,7 @@ pub const CXX_WRAPPER: &str = "hammer-zig-cxx";
/// ── POR QUÉ (medido 2026-08-28) ───────────────────────────────────────────────────────────── /// ── POR QUÉ (medido 2026-08-28) ─────────────────────────────────────────────────────────────
/// Construir `zlib` en dos máquinas dio el MISMO `ArtifactHash` y **bytes distintos**: /// Construir `zlib` en dos máquinas dio el MISMO `ArtifactHash` y **bytes distintos**:
/// `usr/lib/libz.a` divergía sólo en la cabecera `ar` del miembro `/` (la tabla de símbolos), /// `usr/lib/libz.a` divergía sólo en la cabecera `ar` del miembro `/` (la tabla de símbolos),
/// con la hora de pared del build. `hammer why-differs` lo nombró exacto. /// con la hora de pared del build. `takana why-differs` lo nombró exacto.
/// ///
/// La causa NO es `zig ar` (llvm-ar sella mtime 0) ni el binutils del rootfs Alpine (que Alpine /// La causa NO es `zig ar` (llvm-ar sella mtime 0) ni el binutils del rootfs Alpine (que Alpine
/// compila con `--enable-deterministic-archives`): es **nuestro propio `recipes/binutils.toml`**, /// compila con `--enable-deterministic-archives`): es **nuestro propio `recipes/binutils.toml`**,
@@ -577,7 +577,7 @@ pub const CXX_WRAPPER: &str = "hammer-zig-cxx";
pub const AR_WRAPPER: &str = "ar"; pub const AR_WRAPPER: &str = "ar";
pub const RANLIB_WRAPPER: &str = "ranlib"; pub const RANLIB_WRAPPER: &str = "ranlib";
/// Materializa en el rootfs dos wrappers de linker-driver (`hammer-zig-cc`/`-cxx`) que envuelven a /// Materializa en el rootfs dos wrappers de linker-driver (`takana-zig-cc`/`-cxx`) que envuelven a
/// `zig cc`/`zig c++`. Su ÚNICO efecto es QUITAR `-static` cuando el link REQUIERE enlace dinámico. /// `zig cc`/`zig c++`. Su ÚNICO efecto es QUITAR `-static` cuando el link REQUIERE enlace dinámico.
/// ///
/// El harness inyecta `LDFLAGS=-static` para las recetas `link=static` (binario monolítico que pide /// El harness inyecta `LDFLAGS=-static` para las recetas `link=static` (binario monolítico que pide
+2 -2
View File
@@ -3,8 +3,8 @@
//! Convierte la mutación serializada en una `Recipe` efímera + un archivo de patch //! Convierte la mutación serializada en una `Recipe` efímera + un archivo de patch
//! temporal, y delega en `build` para sellar el artefacto en el store. Luego se hidrata. //! temporal, y delega en `build` para sellar el artefacto en el store. Luego se hidrata.
//! //!
//! Este módulo vive en `hammer-build` (no en `hammer-core`) porque depende del store y del //! Este módulo vive en `takana-build` (no en `takana-core`) porque depende del store y del
//! sandbox. `hammer-core` debe permanecer puro (sin red ni mounts) para que el watcher y los //! sandbox. `takana-core` debe permanecer puro (sin red ni mounts) para que el watcher y los
//! tests no arrastren toda la cadena del lab. //! tests no arrastren toda la cadena del lab.
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
@@ -3,7 +3,7 @@
//! EL FALLO (medido 2026-08-30, antes de `no_escapa`): hidratar es `target_fhs.join(rel)` y //! EL FALLO (medido 2026-08-30, antes de `no_escapa`): hidratar es `target_fhs.join(rel)` y
//! escribir por ruta. Un artefacto que trae `usr/share/pkg → /algún/lado` deja ese symlink puesto //! escribir por ruta. 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 //! —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` //! `usr/share/pkg/archivo` **fuera del root**, devolviendo `Ok(1)`. Con `takana hydrate --into`
//! sobre una imagen, eso es escribir en el sistema anfitrión diciendo que todo fue bien. //! sobre una imagen, eso es escribir en el sistema anfitrión diciendo que todo fue bien.
//! //!
//! El corpus no lo disparaba (de 160 symlinks absolutos ninguno apunta a un directorio, y de //! El corpus no lo disparaba (de 160 symlinks absolutos ninguno apunta a un directorio, y de
+3 -3
View File
@@ -1,12 +1,12 @@
//! Guardián de SDD 25 §7-H4: **ningún camino de spawn de hammer usa `pre_exec`**. //! Guardián de SDD 25 §7-H4: **ningún camino de spawn de takana usa `pre_exec`**.
//! //!
//! POR QUÉ. `std::process::Command` de Rust usa `posix_spawn` cuando puede, y `posix_spawn` es //! POR QUÉ. `std::process::Command` de Rust usa `posix_spawn` cuando puede, y `posix_spawn` es
//! PLANO respecto al tamaño del padre: a 256 MB tocados, `fork+exec` costó 3 949 µs contra 231 µs //! PLANO respecto al tamaño del padre: a 256 MB tocados, `fork+exec` costó 3 949 µs contra 231 µs
//! (SDD 25 T1, **17×**). En cuanto se le pone un `pre_exec`, Rust apaga ese camino y cae a //! (SDD 25 T1, **17×**). En cuanto se le pone un `pre_exec`, Rust apaga ese camino y cae a
//! `fork+exec`, que copia el espacio de direcciones del padre. hammer lanza builds desde un //! `fork+exec`, que copia el espacio de direcciones del padre. takana lanza builds desde un
//! proceso que puede tener cientos de MB mapeados. //! proceso que puede tener cientos de MB mapeados.
//! //!
//! Hoy `hammer-build::sandbox` lanza `bwrap` sin `pre_exec` — y hasta ahora eso era SUERTE, no una //! Hoy `takana-build::sandbox` lanza `bwrap` sin `pre_exec` — y hasta ahora eso era SUERTE, no una
//! regla: nadie lo comprobaba. Esto lo vuelve una regla. //! regla: nadie lo comprobaba. Esto lo vuelve una regla.
//! //!
//! CUÁNDO SE PUEDE APAGAR. Si algún día hace falta de verdad (un `setsid`, un `unshare` que bwrap //! CUÁNDO SE PUEDE APAGAR. Si algún día hace falta de verdad (un `setsid`, un `unshare` que bwrap
+3 -3
View File
@@ -7,10 +7,10 @@ authors.workspace = true
repository.workspace = true repository.workspace = true
description = "El binario `takana`: orquesta build, hydrate, try/commit, apply/export y ctl." description = "El binario `takana`: orquesta build, hydrate, try/commit, apply/export y ctl."
# ADR 0016 — renombre hammer→takana, etapa 2. Los DOS binarios se emiten a # ADR 0016 — renombre takana→takana, etapa 2. Los DOS binarios se emiten a
# propósito, en vez de un symlink: la siembra de la granja excluye `/target`, así # propósito, en vez de un symlink: la siembra de la granja excluye `/target`, así
# que un symlink hecho en el hub no existiría en el worker, y `cargo clean` lo # que un symlink hecho en el hub no existiría en el worker, y `cargo clean` lo
# borra. Dos `[[bin]]` sobreviven a las dos cosas. `hammer` se retira en la etapa 6. # borra. Dos `[[bin]]` sobreviven a las dos cosas. `takana` se retira en la etapa 6.
[[bin]] [[bin]]
name = "takana" name = "takana"
path = "src/main.rs" path = "src/main.rs"
@@ -21,7 +21,7 @@ path = "src/main.rs"
[features] [features]
default = [] default = []
# Activa el traductor LLM real (Claude API) detrás del flag `hammer ai --llm`. # Activa el traductor LLM real (Claude API) detrás del flag `takana ai --llm`.
# Sin esta feature, `--llm` falla con un mensaje claro pidiendo recompilar. # Sin esta feature, `--llm` falla con un mensaje claro pidiendo recompilar.
llm-claude = ["takana-agent/llm-claude"] llm-claude = ["takana-agent/llm-claude"]
+6 -6
View File
@@ -1,4 +1,4 @@
//! Importador `Alpine APKBUILD → receta hammer` (Etapa G, fuente #2). //! Importador `Alpine APKBUILD → receta takana` (Etapa G, fuente #2).
//! //!
//! **Por qué Alpine y no sólo nix:** Alpine ya porta miles de paquetes a **musl** (y a menudo //! **Por qué Alpine y no sólo nix:** Alpine ya porta miles de paquetes a **musl** (y a menudo
//! estático), CON los parches de portabilidad. Eso es justo lo que un import de nixpkgs PIERDE (los //! estático), CON los parches de portabilidad. Eso es justo lo que un import de nixpkgs PIERDE (los
@@ -8,12 +8,12 @@
//! Este importador PARSEA el APKBUILD (no lo ejecuta — es shell; sourcearlo correría código ajeno) //! Este importador PARSEA el APKBUILD (no lo ejecuta — es shell; sourcearlo correría código ajeno)
//! y extrae: pkgname/pkgver, la URL de la fuente, **los `.patch` (los carga a `source.patches`)**, //! y extrae: pkgname/pkgver, la URL de la fuente, **los `.patch` (los carga a `source.patches`)**,
//! makedepends → deps, y `build()/package()` → fases (best-effort: son shell de abuild con CHOST/ //! makedepends → deps, y `build()/package()` → fases (best-effort: son shell de abuild con CHOST/
//! --shared/etc. ⇒ necesitan adaptación al lab estático de hammer). Es un PUNTO DE PARTIDA, pero //! --shared/etc. ⇒ necesitan adaptación al lab estático de takana). Es un PUNTO DE PARTIDA, pero
//! uno que YA incluye el trabajo de musl — mucho más cerca de compilar que un import crudo. //! uno que YA incluye el trabajo de musl — mucho más cerca de compilar que un import crudo.
use std::collections::BTreeMap; use std::collections::BTreeMap;
/// Convierte el texto de un APKBUILD en el TOML de una receta hammer. /// Convierte el texto de un APKBUILD en el TOML de una receta takana.
pub fn recipe_from_apkbuild(text: &str) -> Result<String, String> { pub fn recipe_from_apkbuild(text: &str) -> Result<String, String> {
let vars = parse_vars(text); let vars = parse_vars(text);
let get = |k: &str| vars.get(k).cloned().unwrap_or_default(); let get = |k: &str| vars.get(k).cloned().unwrap_or_default();
@@ -61,7 +61,7 @@ pub fn recipe_from_apkbuild(text: &str) -> Result<String, String> {
}; };
// build-deps: SÓLO `makedepends`. El `depends` de abuild es RUNTIME (p.ej. gzip depends="less" // build-deps: SÓLO `makedepends`. El `depends` de abuild es RUNTIME (p.ej. gzip depends="less"
// para `zless`) — no hace falta para compilar y rompería `hammer build` (busca recipes/less.toml). // para `zless`) — no hace falta para compilar y rompería `takana build` (busca recipes/less.toml).
// Lo emitimos como comentario de provenance, no como `[deps] build`. (checkdepends ni se lee: no // Lo emitimos como comentario de provenance, no como `[deps] build`. (checkdepends ni se lee: no
// corremos la suite de tests del paquete.) // corremos la suite de tests del paquete.)
let self_prefix = format!("{pkgname}-"); let self_prefix = format!("{pkgname}-");
@@ -265,10 +265,10 @@ fn func_body(text: &str, name: &str) -> Option<String> {
} }
} }
/// Traduce las variables de abuild a su equivalente en el lab de hammer. Substituciones SEGURAS /// Traduce las variables de abuild a su equivalente en el lab de takana. Substituciones SEGURAS
/// (inequívocas): `$pkgdir`→`/out` (el DESTDIR del lab), `$pkgname`→nombre, `$pkgver`→versión. /// (inequívocas): `$pkgdir`→`/out` (el DESTDIR del lab), `$pkgname`→nombre, `$pkgver`→versión.
/// NO toca `$CBUILD/$CHOST/$srcdir/$builddir` ni `--shared`. `$CBUILD/$CHOST` los RESUELVE el lab en /// NO toca `$CBUILD/$CHOST/$srcdir/$builddir` ni `--shared`. `$CBUILD/$CHOST` los RESUELVE el lab en
/// runtime (env con el triple nativo saneado, Etapa G Fase 3 — `hammer-build`); `$srcdir/$builddir` y /// runtime (env con el triple nativo saneado, Etapa G Fase 3 — `takana-build`); `$srcdir/$builddir` y
/// `--shared` quedan para el humano. `make DESTDIR="$pkgdir"` → `make DESTDIR="/out"`. /// `--shared` quedan para el humano. `make DESTDIR="$pkgdir"` → `make DESTDIR="/out"`.
/// ///
/// Además ENVUELVE el cuerpo en una función shell: abuild ejecuta `build()/package()` COMO funciones, /// Además ENVUELVE el cuerpo en una función shell: abuild ejecuta `build()/package()` COMO funciones,
+6 -6
View File
@@ -1,4 +1,4 @@
//! `hammer kernel` — el armador de kernel (SDD 22). //! `takana kernel` — el armador de kernel (SDD 22).
//! //!
//! Superficie en inglés (Regla 7.bis), mensajes en castellano. El contrato con las UIs es JSON //! Superficie en inglés (Regla 7.bis), mensajes en castellano. El contrato con las UIs es JSON
//! estable, mismo patrón que `/run/hammer/boot-graph.json`. //! estable, mismo patrón que `/run/hammer/boot-graph.json`.
@@ -123,7 +123,7 @@ pub enum KernelCmd {
/// ///
/// Sin esto la UI miente: un `-e FOO` cuya dependencia no se cumple se pierde en silencio. /// Sin esto la UI miente: un `-e FOO` cuya dependencia no se cumple se pierde en silencio.
DiffBack { DiffBack {
/// El JSON que emitió `hammer kernel plan --out`. /// El JSON que emitió `takana kernel plan --out`.
#[arg(long)] #[arg(long)]
plan: PathBuf, plan: PathBuf,
/// El `.config` resultante (el que la receta instala en `/out/boot/config-<versión>`). /// El `.config` resultante (el que la receta instala en `/out/boot/config-<versión>`).
@@ -137,7 +137,7 @@ pub enum KernelCmd {
/// No corre sin `--objective`: la regla aplicada global rechazaría recetas sanas (el kernel de /// No corre sin `--objective`: la regla aplicada global rechazaría recetas sanas (el kernel de
/// QEMU apaga USB, HID e INPUT a propósito). Sale distinto de cero si bloquea. /// QEMU apaga USB, HID e INPUT a propósito). Sale distinto de cero si bloquea.
Gate { Gate {
/// El JSON que emitió `hammer kernel plan --out`. Con `--config` es opcional: sólo sirve /// El JSON que emitió `takana kernel plan --out`. Con `--config` es opcional: sólo sirve
/// para atribuir cada pérdida a su bundle. /// para atribuir cada pérdida a su bundle.
#[arg(long)] #[arg(long)]
plan: Option<PathBuf>, plan: Option<PathBuf>,
@@ -152,7 +152,7 @@ pub enum KernelCmd {
/// Id de objetivo del catálogo (`qemu-serial`, `servidor`, `metal-escritorio`, `portatil`). /// Id de objetivo del catálogo (`qemu-serial`, `servidor`, `metal-escritorio`, `portatil`).
#[arg(long)] #[arg(long)]
objective: String, objective: String,
/// Hardware de la máquina DESTINO, capturado con `hammer kernel hw --out`. Sin esto se /// Hardware de la máquina DESTINO, capturado con `takana kernel hw --out`. Sin esto se
/// mira el de esta máquina, que no siempre es la misma. /// mira el de esta máquina, que no siempre es la misma.
#[arg(long)] #[arg(long)]
devices: Option<PathBuf>, devices: Option<PathBuf>,
@@ -1339,7 +1339,7 @@ fn artifact_of(path: &Path) -> Option<String> {
} }
/// Dónde viven las recetas de kernel cuando nadie pasa `--recipes`. La segunda es la de las /// Dónde viven las recetas de kernel cuando nadie pasa `--recipes`. La segunda es la de las
/// recetas DERIVADAS que emite `hammer kernel plan` (p. ej. `linux-gioser`). /// recetas DERIVADAS que emite `takana kernel plan` (p. ej. `linux-gioser`).
const RECIPE_DEFAULTS: [&str; 2] = ["recipes", "docs/state/kernel-plans"]; const RECIPE_DEFAULTS: [&str; 2] = ["recipes", "docs/state/kernel-plans"];
/// Nombre del directorio del store (`<64 hex>-<nombre>`) que contiene esta ruta. /// Nombre del directorio del store (`<64 hex>-<nombre>`) que contiene esta ruta.
@@ -1395,7 +1395,7 @@ fn receta_de(artifact: &str, dirs: &[PathBuf]) -> Option<takana_core::Recipe> {
/// Le presta un catálogo a una receta que vive fuera de uno. /// Le presta un catálogo a una receta que vive fuera de uno.
/// ///
/// Las recetas DERIVADAS (`hammer kernel plan --recipe-out`) se guardan a propósito fuera de /// Las recetas DERIVADAS (`takana kernel plan --recipe-out`) se guardan a propósito fuera de
/// `recipes/`: dejarlas ahí las mete en el grafo compartido como deuda. Pero sus `deps.build` son /// `recipes/`: dejarlas ahí las mete en el grafo compartido como deuda. Pero sus `deps.build` son
/// las del catálogo padre, así que desde su directorio no resuelven y el hash no se puede calcular /// las del catálogo padre, así que desde su directorio no resuelven y el hash no se puede calcular
/// — y sin hash no hay vigencia que comparar. Acá se les apunta el `base_dir` al primer directorio /// — y sin hash no hay vigencia que comparar. Acá se les apunta el `base_dir` al primer directorio
+17 -17
View File
@@ -1,4 +1,4 @@
//! `hammer` — el punto de entrada de todo flujo manual. Ver `docs/`. //! `takana` — el punto de entrada de todo flujo manual. Ver `docs/`.
//! //!
//! Los subcomandos están mapeados a las fases del roadmap (`docs/10-roadmap.md`). Los que aún //! Los subcomandos están mapeados a las fases del roadmap (`docs/10-roadmap.md`). Los que aún
//! no están implementados devuelven un error claro indicando su fase, para que el esqueleto //! no están implementados devuelven un error claro indicando su fase, para que el esqueleto
@@ -310,8 +310,8 @@ enum Cmd {
db: PathBuf, db: PathBuf,
}, },
/// [Etapa G] Importa una receta DESDE nixpkgs: toma el JSON normalizado que produce /// [Etapa G] Importa una receta DESDE nixpkgs: toma el JSON normalizado que produce
/// `scripts/nix-import.sh` (vía `nix eval`) y emite una receta hammer (`.toml`). Trae la /// `scripts/nix-import.sh` (vía `nix eval`) y emite una receta takana (`.toml`). Trae la
/// RECETA (source+hash+deps+flags), nunca el binario del cache de nix — hammer reconstruye. /// RECETA (source+hash+deps+flags), nunca el binario del cache de nix — takana reconstruye.
/// Es un PUNTO DE PARTIDA: el build (zig/musl) suele necesitar adaptación por paquete. /// Es un PUNTO DE PARTIDA: el build (zig/musl) suele necesitar adaptación por paquete.
ImportNix { ImportNix {
/// JSON normalizado del paquete nix. `-` o ausente ⇒ lee de stdin. /// JSON normalizado del paquete nix. `-` o ausente ⇒ lee de stdin.
@@ -323,7 +323,7 @@ enum Cmd {
}, },
/// [Etapa G] Importa una receta DESDE un APKBUILD de Alpine (aports). Crucial: carga los /// [Etapa G] Importa una receta DESDE un APKBUILD de Alpine (aports). Crucial: carga los
/// `.patch` de musl de Alpine — lo que un import de nix pierde — así está MUCHO más cerca de /// `.patch` de musl de Alpine — lo que un import de nix pierde — así está MUCHO más cerca de
/// compilar en el lab estático de hammer. `scripts/alpine-import.sh <pkg>` baja el APKBUILD. /// compilar en el lab estático de takana. `scripts/alpine-import.sh <pkg>` baja el APKBUILD.
ImportAlpine { ImportAlpine {
/// El APKBUILD (texto). `-` o ausente ⇒ stdin. /// El APKBUILD (texto). `-` o ausente ⇒ stdin.
#[arg(default_value = "-")] #[arg(default_value = "-")]
@@ -372,7 +372,7 @@ enum Cmd {
/// Compile por el bus. Si no, source_patch se omite con warning. /// Compile por el bus. Si no, source_patch se omite con warning.
#[arg(long)] #[arg(long)]
bus: Option<PathBuf>, bus: Option<PathBuf>,
/// Raíz de overlays para `try`. Default coincide con `hammer try`. /// Raíz de overlays para `try`. Default coincide con `takana try`.
#[arg(long)] #[arg(long)]
state_root: Option<PathBuf>, state_root: Option<PathBuf>,
/// Si está, activa el bucle de auto-reparación: tras aplicar, espera en el bus /// Si está, activa el bucle de auto-reparación: tras aplicar, espera en el bus
@@ -398,10 +398,10 @@ enum Cmd {
/// [Fase 6] Evalúa una expresión del mini-lenguaje (SDD 08 §6) contra el sistema local. /// [Fase 6] Evalúa una expresión del mini-lenguaje (SDD 08 §6) contra el sistema local.
/// Forma: `kind:value`. Kinds soportados: `bin`, `file`, `pin`, `service`, `depends`. /// Forma: `kind:value`. Kinds soportados: `bin`, `file`, `pin`, `service`, `depends`.
/// Ejemplos: /// Ejemplos:
/// hammer query bin:grep /// takana query bin:grep
/// hammer query file:/etc/hosts /// takana query file:/etc/hosts
/// hammer query depends:/usr/bin/curl /// takana query depends:/usr/bin/curl
/// hammer query pin:musl --base-ref base.json /// takana query pin:musl --base-ref base.json
Query { Query {
/// La expresión a evaluar. /// La expresión a evaluar.
expr: String, expr: String,
@@ -509,8 +509,8 @@ enum BootCmd {
/// ///
/// EN PRODUCCIÓN NO SE USA (PLAN-KIKIN §4.1/§9, 2026-07-16): mirada real NUNCA sale — sostiene /// EN PRODUCCIÓN NO SE USA (PLAN-KIKIN §4.1/§9, 2026-07-16): mirada real NUNCA sale — sostiene
/// el DRM master del greeter al escritorio para no re-modesetear (parpadeo). El camino real es: /// el DRM master del greeter al escritorio para no re-modesetear (parpadeo). El camino real es:
/// arje-zero publica el grafo al arrancar (`hammer boot graph`), mirada escribe boot-select y /// arje-zero publica el grafo al arrancar (`takana boot graph`), mirada escribe boot-select y
/// dispara el reboot por el bus de arje, y arje-zero corre `hammer boot activate --from-select` /// dispara el reboot por el bus de arje, y arje-zero corre `takana boot activate --from-select`
/// en su secuencia de apagado. /// en su secuencia de apagado.
Menu { Menu {
/// Comando del compositor a lanzar (se parte por espacios). El primer token es el binario. /// Comando del compositor a lanzar (se parte por espacios). El primer token es el binario.
@@ -693,7 +693,7 @@ enum BootstrapCmd {
/// Directorio de recetas a embeber. /// Directorio de recetas a embeber.
#[arg(long, default_value = "recipes")] #[arg(long, default_value = "recipes")]
recipes: String, recipes: String,
/// Binario `hammer` estático a instalar en `/usr/bin/hammer`. /// Binario `takana` estático a instalar en `/usr/bin/hammer`.
#[arg(long, default_value = "target/x86_64-unknown-linux-musl/release/hammer")] #[arg(long, default_value = "target/x86_64-unknown-linux-musl/release/hammer")]
hammer_bin: String, hammer_bin: String,
/// Toolchain del sandbox de build → `/toolchain` (rootfs Alpine de `.dev-fs`). /// Toolchain del sandbox de build → `/toolchain` (rootfs Alpine de `.dev-fs`).
@@ -709,7 +709,7 @@ enum BootstrapCmd {
/// Caché de fuentes (`work/`) a embeber en `/work` para un rebuild offline. /// Caché de fuentes (`work/`) a embeber en `/work` para un rebuild offline.
#[arg(long)] #[arg(long)]
work_cache: Option<String>, work_cache: Option<String>,
/// Pieza del toolchain construida por hammer desde fuente (variante b, SDD 11 §7.2b) que pisa /// Pieza del toolchain construida por takana desde fuente (variante b, SDD 11 §7.2b) que pisa
/// la de Alpine en `/toolchain`. Formato `name=hash[:rel_path]`; `rel_path` por defecto /// la de Alpine en `/toolchain`. Formato `name=hash[:rel_path]`; `rel_path` por defecto
/// `usr/bin/<name>`. Repetible. Ej.: `--swap make=fbad44ac…`. Cada swap entra al hash lógico. /// `usr/bin/<name>`. Repetible. Ej.: `--swap make=fbad44ac…`. Cada swap entra al hash lógico.
#[arg(long = "swap", value_name = "name=hash[:rel_path]")] #[arg(long = "swap", value_name = "name=hash[:rel_path]")]
@@ -1450,7 +1450,7 @@ fn main() -> anyhow::Result<()> {
} }
/// Activa un nodo del grafo de arranque, imprime el resultado y republica el grafo (la vista de mirada /// Activa un nodo del grafo de arranque, imprime el resultado y republica el grafo (la vista de mirada
/// debe reflejar el nuevo nodo vivo). Compartido por `hammer boot activate` y `hammer boot menu`. /// debe reflejar el nuevo nodo vivo). Compartido por `takana boot activate` y `takana boot menu`.
fn activate_and_report( fn activate_and_report(
root: &std::path::Path, root: &std::path::Path,
state_root: &std::path::Path, state_root: &std::path::Path,
@@ -2208,7 +2208,7 @@ fn run_install(
Some(&pkg_name), // reproduce con el nombre del paquete ⇒ cache-hit del corpus, sin duplicar Some(&pkg_name), // reproduce con el nombre del paquete ⇒ cache-hit del corpus, sin duplicar
)?; )?;
// Registrar en la DB de instalados (salvo dry-run de schema). Permite `hammer uninstall`. // Registrar en la DB de instalados (salvo dry-run de schema). Permite `takana uninstall`.
if !skip_source_patch { if !skip_source_patch {
let mut idb = takana_core::InstalledDb::load(db_path)?; let mut idb = takana_core::InstalledDb::load(db_path)?;
let files: Vec<String> = created.iter().map(|p| p.to_string_lossy().into_owned()).collect(); let files: Vec<String> = created.iter().map(|p| p.to_string_lossy().into_owned()).collect();
@@ -2242,7 +2242,7 @@ fn run_import_nix(file: &str, out: Option<&std::path::Path>) -> anyhow::Result<(
let pkg: nix_import::NixPkg = serde_json::from_str(&json) let pkg: nix_import::NixPkg = serde_json::from_str(&json)
.map_err(|e| anyhow::anyhow!("JSON normalizado de nix inválido: {e}"))?; .map_err(|e| anyhow::anyhow!("JSON normalizado de nix inválido: {e}"))?;
let toml = nix_import::to_recipe_toml(&pkg).map_err(|e| anyhow::anyhow!(e))?; let toml = nix_import::to_recipe_toml(&pkg).map_err(|e| anyhow::anyhow!(e))?;
// Sanity: la receta emitida debe parsear como Recipe de hammer. // Sanity: la receta emitida debe parsear como Recipe de takana.
takana_core::Recipe::from_toml(&toml) takana_core::Recipe::from_toml(&toml)
.map_err(|e| anyhow::anyhow!("la receta importada no parsea (bug del importador): {e}"))?; .map_err(|e| anyhow::anyhow!("la receta importada no parsea (bug del importador): {e}"))?;
match out { match out {
@@ -3089,7 +3089,7 @@ fn collect_patches_inline(recipe: &takana_core::Recipe) -> Option<String> {
Some(all) Some(all)
} }
/// Ejecuta el bucle agéntico de Fase 6 vía `hammer-agent`. Acepta una intención NL + /// Ejecuta el bucle agéntico de Fase 6 vía `takana-agent`. Acepta una intención NL +
/// catálogo YAML que mapea intentos exactos a `.swm`s pre-armados. La salida es un /// catálogo YAML que mapea intentos exactos a `.swm`s pre-armados. La salida es un
/// `Proposal` legible en stdout + el siguiente paso recomendado al humano. /// `Proposal` legible en stdout + el siguiente paso recomendado al humano.
fn run_ai( fn run_ai(
+14 -14
View File
@@ -1,9 +1,9 @@
//! Importador `nix → receta hammer` (Etapa G: poblar el catálogo desde nixpkgs). //! Importador `nix → receta takana` (Etapa G: poblar el catálogo desde nixpkgs).
//! //!
//! El problema: a mano, 34 recetas = userland desierto; un distro usable necesita miles de //! El problema: a mano, 34 recetas = userland desierto; un distro usable necesita miles de
//! paquetes. nixpkgs es el mayor set de recetas DESDE FUENTE (sources pineados+hash, deps //! paquetes. nixpkgs es el mayor set de recetas DESDE FUENTE (sources pineados+hash, deps
//! explícitas), así que es la semilla natural del catálogo. Este importador toma la *receta* de //! explícitas), así que es la semilla natural del catálogo. Este importador toma la *receta* de
//! nix (qué bajar, flags, deps) y la reexpresa como una `Recipe` de hammer — **nunca el binario** //! nix (qué bajar, flags, deps) y la reexpresa como una `Recipe` de takana — **nunca el binario**
//! del cache de nix (eso rompería "verificar, no confiar"; hammer reconstruye desde fuente igual). //! del cache de nix (eso rompería "verificar, no confiar"; hammer reconstruye desde fuente igual).
//! //!
//! Consume un JSON NORMALIZADO (lo produce `scripts/nix-import.sh` vía `nix eval --apply`), no el //! Consume un JSON NORMALIZADO (lo produce `scripts/nix-import.sh` vía `nix eval --apply`), no el
@@ -53,8 +53,8 @@ pub struct RawSource {
pub output_hash_mode: String, pub output_hash_mode: String,
} }
/// El origen ya clasificado para hammer. `fetchurl` plano → `tarball+sha256`; GitHub (codeload) → /// El origen ya clasificado para takana. `fetchurl` plano → `tarball+sha256`; GitHub (codeload) →
/// `repo+commit` (hammer pinea por commit y reproduce clonando, sin depender del hash NAR de nix). /// `repo+commit` (takana pinea por commit y reproduce clonando, sin depender del hash NAR de nix).
#[derive(Debug, PartialEq, Eq)] #[derive(Debug, PartialEq, Eq)]
pub enum NixSource { pub enum NixSource {
Tarball { url: String, sha256: String }, Tarball { url: String, sha256: String },
@@ -85,7 +85,7 @@ pub fn classify_source(raw: &RawSource) -> Result<NixSource, String> {
} }
/// Traduce el esquema `mirror://<sitio>/...` de nix a una URL concreta. nix resuelve estos /// Traduce el esquema `mirror://<sitio>/...` de nix a una URL concreta. nix resuelve estos
/// mirrors en tiempo de eval; un import los deja literales y el fetch de hammer (curl) no los /// mirrors en tiempo de eval; un import los deja literales y el fetch de takana (curl) no los
/// entiende. Mapeamos los más comunes a un mirror real; lo no mapeado se deja igual + el usuario /// entiende. Mapeamos los más comunes a un mirror real; lo no mapeado se deja igual + el usuario
/// elige espejo. (nixpkgs tiene la lista completa en `pkgs/build-support/fetchurl/mirrors.nix`.) /// elige espejo. (nixpkgs tiene la lista completa en `pkgs/build-support/fetchurl/mirrors.nix`.)
fn expand_nix_mirror(url: &str) -> String { fn expand_nix_mirror(url: &str) -> String {
@@ -138,7 +138,7 @@ fn parse_github_url(url: &str) -> Option<(String, String, String)> {
None None
} }
/// Convierte una receta nix normalizada en el TOML de una `Recipe` de hammer. El resultado es un /// Convierte una receta nix normalizada en el TOML de una `Recipe` de takana. El resultado es un
/// PUNTO DE PARTIDA: lleva comentarios marcando lo que falta adaptar (toolchain, nombres de deps). /// PUNTO DE PARTIDA: lleva comentarios marcando lo que falta adaptar (toolchain, nombres de deps).
pub fn to_recipe_toml(pkg: &NixPkg) -> Result<String, String> { pub fn to_recipe_toml(pkg: &NixPkg) -> Result<String, String> {
let name = sanitize_name(&pkg.pname); let name = sanitize_name(&pkg.pname);
@@ -156,7 +156,7 @@ pub fn to_recipe_toml(pkg: &NixPkg) -> Result<String, String> {
}; };
// deps: build = nativeBuildInputs ++ buildInputs (por nombre nix; el humano remapea al corpus // deps: build = nativeBuildInputs ++ buildInputs (por nombre nix; el humano remapea al corpus
// de hammer si difieren — p.ej. nix `pkg-config` ≈ hammer `pkgconf`). // de takana si difieren — p.ej. nix `pkg-config` ≈ takana `pkgconf`).
let mut deps: Vec<String> = Vec::new(); let mut deps: Vec<String> = Vec::new();
for d in pkg.native_build_inputs.iter().chain(pkg.build_inputs.iter()) { for d in pkg.native_build_inputs.iter().chain(pkg.build_inputs.iter()) {
let n = sanitize_name(d); let n = sanitize_name(d);
@@ -170,7 +170,7 @@ pub fn to_recipe_toml(pkg: &NixPkg) -> Result<String, String> {
// zlib, rustls vs openssl) o lo dejan opt-in (pcre2). Cargo resuelve el grafo Rust por vendoring; // zlib, rustls vs openssl) o lo dejan opt-in (pcre2). Cargo resuelve el grafo Rust por vendoring;
// un sys-lib C que SÍ haga falta es adaptación per-paquete (patch/feature, p.ej. // un sys-lib C que SÍ haga falta es adaptación per-paquete (patch/feature, p.ej.
// ripgrep-no-jemalloc), no una dep de corpus. Las recetas Rust validadas (ripgrep/uutils) NO // ripgrep-no-jemalloc), no una dep de corpus. Las recetas Rust validadas (ripgrep/uutils) NO
// declaran `[deps]`; emitirlas rompe `hammer build` (busca recipes/<dep>.toml). Quedan como // declaran `[deps]`; emitirlas rompe `takana build` (busca recipes/<dep>.toml). Quedan como
// COMENTARIO de provenance (no se pierden: señalan qué C podría necesitarse). // COMENTARIO de provenance (no se pierden: señalan qué C podría necesitarse).
let deps_block = if pkg.is_go { let deps_block = if pkg.is_go {
// Go: la única build-dep es el toolchain `go` (recipes/go.toml). Los buildInputs C de nix // Go: la única build-dep es el toolchain `go` (recipes/go.toml). Los buildInputs C de nix
@@ -191,7 +191,7 @@ pub fn to_recipe_toml(pkg: &NixPkg) -> Result<String, String> {
format!("\n[deps]\nbuild = [{list}]\n") format!("\n[deps]\nbuild = [{list}]\n")
}; };
// Rust (buildRustPackage): emite la plantilla Cargo de hammer — `--bin <bin>` (cargo exige UN // Rust (buildRustPackage): emite la plantilla Cargo de takana — `--bin <bin>` (cargo exige UN
// target) + install copiando `target/release/<bin>` a /out (mismo patrón que la receta ripgrep). // target) + install copiando `target/release/<bin>` a /out (mismo patrón que la receta ripgrep).
// El bin = meta.mainProgram de nix (ripgrep→rg), o el pname. Esto deja la receta build-ready. // El bin = meta.mainProgram de nix (ripgrep→rg), o el pname. Esto deja la receta build-ready.
let (flags_line, rust_install) = if pkg.is_rust { let (flags_line, rust_install) = if pkg.is_rust {
@@ -230,7 +230,7 @@ pub fn to_recipe_toml(pkg: &NixPkg) -> Result<String, String> {
let toml = format!( let toml = format!(
"# Importada de nixpkgs por `hammer import-nix` (Etapa G). PUNTO DE PARTIDA, no final:\n\ "# Importada de nixpkgs por `hammer import-nix` (Etapa G). PUNTO DE PARTIDA, no final:\n\
# - el build usa el lab de hammer (zig-cc / musl estático), NO el stdenv de nix revisá\n\ # - el build usa el lab de takana (zig-cc / musl estático), NO el stdenv de nix revisá\n\
# compiler/link/phases y adaptá hasta que compile.\n\ # compiler/link/phases y adaptá hasta que compile.\n\
# - las deps van con su nombre NIX; remapealas a las recetas del corpus si difieren.\n\ # - las deps van con su nombre NIX; remapealas a las recetas del corpus si difieren.\n\
name = \"{name}\"\n\ name = \"{name}\"\n\
@@ -260,7 +260,7 @@ fn is_nix_noise(name: &str) -> bool {
|| name == "auditable-cargo" || name == "auditable-cargo"
|| name.starts_with("auditable-cargo-") || name.starts_with("auditable-cargo-")
|| name == "version-check" || name == "version-check"
// El toolchain Rust ES el lab de hammer (cargo vendorea las crates), NO un paquete dep. // El toolchain Rust ES el lab de takana (cargo vendorea las crates), NO un paquete dep.
|| name == "rustc" || name == "rustc"
|| name == "cargo" || name == "cargo"
|| name == "rust" || name == "rust"
@@ -416,11 +416,11 @@ mod tests {
}"#; }"#;
let pkg: NixPkg = serde_json::from_str(json).unwrap(); let pkg: NixPkg = serde_json::from_str(json).unwrap();
let toml = to_recipe_toml(&pkg).unwrap(); let toml = to_recipe_toml(&pkg).unwrap();
// Parsea como Recipe válida de hammer. // Parsea como Recipe válida de takana.
let recipe = takana_core::Recipe::from_toml(&toml).expect("recipe válida"); let recipe = takana_core::Recipe::from_toml(&toml).expect("recipe válida");
assert_eq!(recipe.name, "hello"); assert_eq!(recipe.name, "hello");
assert_eq!(recipe.version, "2.12.1"); assert_eq!(recipe.version, "2.12.1");
// mirror://gnu/ se traduce a una URL concreta que el fetch de hammer entiende. // mirror://gnu/ se traduce a una URL concreta que el fetch de takana entiende.
assert_eq!(recipe.source.tarball.as_deref(), Some("https://ftp.gnu.org/gnu/hello/hello-2.12.1.tar.gz")); assert_eq!(recipe.source.tarball.as_deref(), Some("https://ftp.gnu.org/gnu/hello/hello-2.12.1.tar.gz"));
assert_eq!( assert_eq!(
recipe.source.sha256.as_deref(), recipe.source.sha256.as_deref(),
@@ -447,7 +447,7 @@ mod tests {
#[test] #[test]
fn rust_buildinputs_are_not_active_deps() { fn rust_buildinputs_are_not_active_deps() {
// nix lista el closure maximal (pcre2/openssl…); para Rust NO deben volverse `[deps]` (cargo // nix lista el closure maximal (pcre2/openssl…); para Rust NO deben volverse `[deps]` (cargo
// vendorea, backend Rust por defecto) — romperían `hammer build`. Quedan como comentario. // vendorea, backend Rust por defecto) — romperían `takana build`. Quedan como comentario.
let json = r#"{ let json = r#"{
"pname": "rg", "version": "14", "pname": "rg", "version": "14",
"source": { "url": "https://github.com/o/r/archive/abc.tar.gz", "output_hash_mode": "recursive" }, "source": { "url": "https://github.com/o/r/archive/abc.tar.gz", "output_hash_mode": "recursive" },
+9 -9
View File
@@ -1,4 +1,4 @@
//! `hammer qorpa` — imágenes ajenas ([ADR 0015](../../../docs/adr/0015-imagenes-ajenas.md)). //! `takana qorpa` — imágenes ajenas ([ADR 0015](../../../docs/adr/0015-imagenes-ajenas.md)).
//! //!
//! Superficie en inglés, mensajes en castellano (`CLAUDE.md` regla 4). //! Superficie en inglés, mensajes en castellano (`CLAUDE.md` regla 4).
//! //!
@@ -88,7 +88,7 @@ pub enum QorpaCmd {
/// [ADR 0015 §Orden 3] Tira la capa mutable y la rehace desde el manifiesto. /// [ADR 0015 §Orden 3] Tira la capa mutable y la rehace desde el manifiesto.
/// ///
/// Es la prueba de D3: si esto duele, es que el `upper` se volvió el activo — y un blob /// Es la prueba de D3: si esto duele, es que el `upper` se volvió el activo — y un blob
/// irreemplazable es justo lo que hammer existe para no tener. /// irreemplazable es justo lo que takana existe para no tener.
Recreate { Recreate {
id: String, id: String,
/// No reinstalar `packages` después de tirar la capa. Deja la instancia como recién creada. /// No reinstalar `packages` después de tirar la capa. Deja la instancia como recién creada.
@@ -130,7 +130,7 @@ pub enum QorpaCmd {
/// ///
/// **Es lo que vuelve cierta la frase «el manifiesto es la verdad».** Sin esto `packages` era /// **Es lo que vuelve cierta la frase «el manifiesto es la verdad».** Sin esto `packages` era
/// una lista que nadie leía: `recreate` tiraba la capa y lo instalado a mano no volvía, así que /// una lista que nadie leía: `recreate` tiraba la capa y lo instalado a mano no volvía, así que
/// el `upper` era el activo de verdad — exactamente el blob irreemplazable que hammer existe /// el `upper` era el activo de verdad — exactamente el blob irreemplazable que takana existe
/// para no tener. /// para no tener.
Provision { Provision {
id: String, id: String,
@@ -146,7 +146,7 @@ pub enum QorpaCmd {
/// [ADR 0015 D8] Registra como imagen un artefacto del store que trae una imagen ajena SELLADA. /// [ADR 0015 D8] Registra como imagen un artefacto del store que trae una imagen ajena SELLADA.
/// ///
/// Es el consumidor de `recipes/steam-runtime-sniper.toml`: la imagen deja de venir de la CDN /// Es el consumidor de `recipes/steam-runtime-sniper.toml`: la imagen deja de venir de la CDN
/// de Valve y viene de nuestro store, replicable con `hammer mirror push` y cubrible por el /// de Valve y viene de nuestro store, replicable con `takana mirror push` y cubrible por el
/// índice firmado (ADR 0014). **La identidad no cambia**: la imagen se registra bajo el sha256 /// índice firmado (ADR 0014). **La identidad no cambia**: la imagen se registra bajo el sha256
/// del ARCHIVO de upstream que el artefacto declara, no bajo su ArtifactHash, así que la que /// del ARCHIVO de upstream que el artefacto declara, no bajo su ArtifactHash, así que la que
/// importa una máquina y la que otra trae con `pull` son la MISMA imagen. /// importa una máquina y la que otra trae con `pull` son la MISMA imagen.
@@ -627,7 +627,7 @@ fn write_manifest(dir: &Path, m: &ImageManifest) -> Result<()> {
// //
// D1 dice que las imágenes ajenas no entran al store, y el motivo es que un rootfs MUTABLE no // D1 dice que las imágenes ajenas no entran al store, y el motivo es que un rootfs MUTABLE no
// reproduce. Sniper no muta —nadie le instala nada adentro— así que sellarlo es una afirmación // reproduce. Sniper no muta —nadie le instala nada adentro— así que sellarlo es una afirmación
// verdadera, y con eso se gana cadena de custodia nuestra: se replica con `hammer mirror push`, su // verdadera, y con eso se gana cadena de custodia nuestra: se replica con `takana mirror push`, su
// digest puede ir al índice firmado (ADR 0014) y una instancia se arma sin depender de la CDN de // digest puede ir al índice firmado (ADR 0014) y una instancia se arma sin depender de la CDN de
// Valve. Esto es lo que convierte ese artefacto en una imagen usable. // Valve. Esto es lo que convierte ese artefacto en una imagen usable.
// //
@@ -731,7 +731,7 @@ struct Instance {
base: String, base: String,
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
distro: Option<String>, distro: Option<String>,
/// Lo que debe estar instalado adentro. **Se instala solo**: `hammer qorpa provision` lo pone /// Lo que debe estar instalado adentro. **Se instala solo**: `takana qorpa provision` lo pone
/// con el gestor de la imagen, y `recreate` lo llama después de tirar la capa. Sin esto el /// con el gestor de la imagen, y `recreate` lo llama después de tirar la capa. Sin esto el
/// `upper` sería el activo y el manifiesto un adorno. /// `upper` sería el activo y el manifiesto un adorno.
#[serde(default, skip_serializing_if = "Vec::is_empty")] #[serde(default, skip_serializing_if = "Vec::is_empty")]
@@ -2027,7 +2027,7 @@ fn medir(p: &Path) -> (u64, bool) {
// El objetivo es el poder de Bedrock Linux —apps de cualquier «stratum» disponibles en todos // El objetivo es el poder de Bedrock Linux —apps de cualquier «stratum» disponibles en todos
// lados— SIN sus formas: nada de un FUSE global en el camino de cada `exec`, nada de arbitrar por // lados— SIN sus formas: nada de un FUSE global en el camino de cada `exec`, nada de arbitrar por
// heurística qué binario gana. Acá no hace falta, porque **el FHS de esta distro ya es una // heurística qué binario gana. Acá no hace falta, porque **el FHS de esta distro ya es una
// proyección**: `hammer hydrate` proyecta artefactos a un árbol, y extender la proyección a // proyección**: `takana hydrate` proyecta artefactos a un árbol, y extender la proyección a
// instancias es idiomático. // instancias es idiomático.
// //
// Tres propiedades que un FUSE no da: **cero costo en runtime** (no hay proceso en el camino // Tres propiedades que un FUSE no da: **cero costo en runtime** (no hay proceso en el camino
@@ -2170,8 +2170,8 @@ fn export(root: &Path, id: &str, into: Option<&Path>, remove: bool) -> Result<()
let dst = bin_dir.join(b); let dst = bin_dir.join(b);
let cuerpo = format!( let cuerpo = format!(
"#!/bin/sh\n\ "#!/bin/sh\n\
# GENERADO por `hammer qorpa export {id}` {MARCA}. No editar: se reescribe.\n\ # GENERADO por `takana qorpa export {id}` {MARCA}. No editar: se reescribe.\n\
# Borralo (o corré `hammer qorpa export {id} --remove`) y la instancia deja de verse.\n\ # Borralo (o corré `takana qorpa export {id} --remove`) y la instancia deja de verse.\n\
exec {hammer} qorpa run{raiz_flag} {id} -- {b} \"$@\"\n", exec {hammer} qorpa run{raiz_flag} {id} -- {b} \"$@\"\n",
hammer = shell_quote(&hammer.display().to_string()), hammer = shell_quote(&hammer.display().to_string()),
b = shell_quote(b), b = shell_quote(b),
+1 -1
View File
@@ -81,7 +81,7 @@ pub struct Proxy {
/// Arranca el proxy en segundo plano. Devuelve el socket a bindear en la jaula. /// Arranca el proxy en segundo plano. Devuelve el socket a bindear en la jaula.
/// ///
/// Los hilos son detached a propósito: viven lo que viva el proceso, que es exactamente lo que dura /// Los hilos son detached a propósito: viven lo que viva el proceso, que es exactamente lo que dura
/// la instancia (`hammer qorpa run` bloquea en bwrap). /// la instancia (`takana qorpa run` bloquea en bwrap).
pub fn arrancar(dir: &Path, socket_real: &Path, permitidos: HashSet<String>) -> io::Result<Proxy> { pub fn arrancar(dir: &Path, socket_real: &Path, permitidos: HashSet<String>) -> io::Result<Proxy> {
std::fs::create_dir_all(dir)?; std::fs::create_dir_all(dir)?;
let socket = dir.join("wayland-0"); let socket = dir.join("wayland-0");
+2 -2
View File
@@ -1,4 +1,4 @@
//! E2E del orquestador `hammer boot menu` (ADR 0010, glue de arranque): emite el grafo, lanza el //! E2E del orquestador `takana boot menu` (ADR 0010, glue de arranque): emite el grafo, lanza el
//! compositor (mirada) y activa el nodo elegido. Probamos el GLUE headless con stubs de compositor — //! compositor (mirada) y activa el nodo elegido. Probamos el GLUE headless con stubs de compositor —
//! el pivot de generaciones ya lo cubren los tests de `boot_graph::activate`. Tres caminos: //! el pivot de generaciones ya lo cubren los tests de `boot_graph::activate`. Tres caminos:
//! A) sin compositor instalado (servidor headless) → emite el grafo y sigue el arranque (exit 0). //! A) sin compositor instalado (servidor headless) → emite el grafo y sigue el arranque (exit 0).
@@ -84,7 +84,7 @@ fn c_seleccion_viaja_de_boot_select_a_activate() {
); );
} }
// --- `hammer boot activate --from-select` idempotente (contrato PLAN-KIKIN §9) --------------------- // --- `takana boot activate --from-select` idempotente (contrato PLAN-KIKIN §9) ---------------------
// arje-zero lo invoca INCONDICIONAL en todo apagado: sin selección debe ser un no-op limpio (exit 0), // arje-zero lo invoca INCONDICIONAL en todo apagado: sin selección debe ser un no-op limpio (exit 0),
// y el fichero sólo se consume si la activación tuvo éxito (si falla, queda para diagnóstico). // y el fichero sólo se consume si la activación tuvo éxito (si falla, queda para diagnóstico).
+1 -1
View File
@@ -1,4 +1,4 @@
//! E2E del CLI `hammer bootstrap stage0` (track posterior, SDD 11). Construye un tarball de //! E2E del CLI `takana bootstrap stage0` (track posterior, SDD 11). Construye un tarball de
//! semilla falso, lo ingiere vía `file://` (sin red) y verifica el camino completo: //! semilla falso, lo ingiere vía `file://` (sin red) y verifica el camino completo:
//! sella + imprime el hash, es idempotente, y un sha256 erróneo falla sin sellar. //! sella + imprime el hash, es idempotente, y un sha256 erróneo falla sin sellar.
+3 -3
View File
@@ -1,4 +1,4 @@
//! H4c e2e: el gate de **colisión de fichero observada** en `hammer install` real. //! H4c e2e: el gate de **colisión de fichero observada** en `takana install` real.
//! //!
//! Reproduce el caso "logo" a nivel de fichero, sin declarar `slots`: dos paquetes escriben el //! Reproduce el caso "logo" a nivel de fichero, sin declarar `slots`: dos paquetes escriben el
//! mismo path. El primero ya está instalado (pre-seed de la DB); al instalar el segundo, el gate //! mismo path. El primero ya está instalado (pre-seed de la DB); al instalar el segundo, el gate
@@ -76,7 +76,7 @@ fn install_aborta_ante_colision_de_fichero_y_force_slots_la_supera() {
); );
} }
/// H4d e2e: `hammer compat <repo>` particiona un repo contra el estado instalado. La búsqueda /// H4d e2e: `takana compat <repo>` particiona un repo contra el estado instalado. La búsqueda
/// del prototipo `wawa-memo` (`filtrar`), ahora sobre el repo real y read-only. /// del prototipo `wawa-memo` (`filtrar`), ahora sobre el repo real y read-only.
#[test] #[test]
fn compat_particiona_el_repo_contra_lo_instalado() { fn compat_particiona_el_repo_contra_lo_instalado() {
@@ -123,7 +123,7 @@ fn compat_particiona_el_repo_contra_lo_instalado() {
assert!(stdout.contains("tema-oscuro"), "stdout: {stdout}"); assert!(stdout.contains("tema-oscuro"), "stdout: {stdout}");
} }
/// H4e e2e: `hammer compat` marca INCOMPATIBLE un paquete cuya dep de runtime divergiste /// H4e e2e: `takana compat` marca INCOMPATIBLE un paquete cuya dep de runtime divergiste
/// (el caso wayland, DERIVADO — sin que el paquete declare `requires`). Read-only: ve el /// (el caso wayland, DERIVADO — sin que el paquete declare `requires`). Read-only: ve el
/// source_patch sin construirlo. /// source_patch sin construirlo.
#[test] #[test]
+2 -2
View File
@@ -1,10 +1,10 @@
//! Roundtrip e2e de Fase 4: construimos un `.swm` a mano (los tipos viven en hammer-core), //! Roundtrip e2e de Fase 4: construimos un `.swm` a mano (los tipos viven en takana-core),
//! lo aplicamos a un prefix temporal, leemos el resultado y verificamos que los archivos //! lo aplicamos a un prefix temporal, leemos el resultado y verificamos que los archivos
//! coinciden con lo esperado. //! coinciden con lo esperado.
//! //!
//! NO ejercitamos `source_patch` porque arrastra todo el lab (red + sandbox); para esos //! NO ejercitamos `source_patch` porque arrastra todo el lab (red + sandbox); para esos
//! casos existen los tests gated en `TAKANA_NETWORK_TESTS`. Aquí cubrimos los caminos puros //! casos existen los tests gated en `TAKANA_NETWORK_TESTS`. Aquí cubrimos los caminos puros
//! de hammer-core: `verify_schema`, `verify_base`, `apply_config_edit`, `apply_file_drop`. //! de takana-core: `verify_schema`, `verify_base`, `apply_config_edit`, `apply_file_drop`.
use std::collections::BTreeMap; use std::collections::BTreeMap;
use std::path::Path; use std::path::Path;
+1 -1
View File
@@ -5,7 +5,7 @@ edition.workspace = true
license.workspace = true license.workspace = true
authors.workspace = true authors.workspace = true
repository.workspace = true repository.workspace = true
description = "Tipos compartidos de hammer: Recipe, Swm, hashing CAS y el store." description = "Tipos compartidos de takana: Recipe, Swm, hashing CAS y el store."
[dependencies] [dependencies]
anyhow.workspace = true anyhow.workspace = true
+2 -2
View File
@@ -3,7 +3,7 @@
//! Estas funciones son **puras** sobre el sistema de archivos: reciben paths absolutos (ya //! Estas funciones son **puras** sobre el sistema de archivos: reciben paths absolutos (ya
//! re-rooteados al overlay o a un prefix de test) y aplican un único `Mutation` cada una. //! re-rooteados al overlay o a un prefix de test) y aplican un único `Mutation` cada una.
//! La orquestación (montar overlay, iterar mutaciones, build de source_patch) vive en //! La orquestación (montar overlay, iterar mutaciones, build de source_patch) vive en
//! `hammer-cli`, no aquí — hammer-core no depende de hammer-build ni del overlay. //! `takana-cli`, no aquí — takana-core no depende de takana-build ni del overlay.
//! //!
//! ## `config_edit` //! ## `config_edit`
//! //!
@@ -31,7 +31,7 @@ use crate::hash::ArtifactHash;
/// Directorio canónico donde `init_rule` materializa una regla por servicio. El init /// Directorio canónico donde `init_rule` materializa una regla por servicio. El init
/// (arje, PID 1) lee este árbol para saber qué supervisar. Es el contrato on-disk entre /// (arje, PID 1) lee este árbol para saber qué supervisar. Es el contrato on-disk entre
/// hammer (declara) y el init (ejecuta) — análogo a `/etc/systemd/system` pero nativo. /// takana (declara) y el init (ejecuta) — análogo a `/etc/systemd/system` pero nativo.
pub const INIT_RULES_DIR: &str = "/etc/hammer/init.d"; pub const INIT_RULES_DIR: &str = "/etc/hammer/init.d";
#[derive(Debug, thiserror::Error)] #[derive(Debug, thiserror::Error)]
+1 -1
View File
@@ -17,7 +17,7 @@
//! caps = ["query", "compile", "inject", "inject-real", "init"] //! caps = ["query", "compile", "inject", "inject-real", "init"]
//! //!
//! [[rule]] //! [[rule]]
//! gid = 994 # p. ej. grupo `hammer` //! gid = 994 # p. ej. grupo `takana`
//! caps = ["query", "compile", "inject", "init"] //! caps = ["query", "compile", "inject", "init"]
//! ``` //! ```
+1 -1
View File
@@ -1,6 +1,6 @@
//! H4b — el gate de **compatibilidad de configs** sobre la receta real (SDD 15 §H4). //! H4b — el gate de **compatibilidad de configs** sobre la receta real (SDD 15 §H4).
//! //!
//! Sube a hammer el modelo de slots que `wawa-memo/src/compat.rs` demostró como //! Sube a takana el modelo de slots que `wawa-memo/src/compat.rs` demostró como
//! prototipo (host/std, 52 tests verdes): una config/paquete declara qué //! prototipo (host/std, 52 tests verdes): una config/paquete declara qué
//! superficies del sistema **reclama** (`Slots::claims`) y **requiere** //! superficies del sistema **reclama** (`Slots::claims`) y **requiere**
//! (`Slots::requires`), ambas por hash. Antes de tocar el sistema, `install` //! (`Slots::requires`), ambas por hash. Antes de tocar el sistema, `install`
+2 -2
View File
@@ -1,4 +1,4 @@
//! `why-differs` — el diffoscope propio de hammer ([SDD 17 §1.2](../../docs/17-cierres-frontera.md)). //! `why-differs` — el diffoscope propio de takana ([SDD 17 §1.2](../../docs/17-cierres-frontera.md)).
//! //!
//! Cuando un artefacto **no reproduce**, el store sólo sabe decir "el hash no coincide". Eso deja el //! Cuando un artefacto **no reproduce**, el store sólo sabe decir "el hash no coincide". Eso deja el
//! trabajo entero al humano: desempacar los dos árboles, `cmp` a mano, adivinar. Este módulo responde //! trabajo entero al humano: desempacar los dos árboles, `cmp` a mano, adivinar. Este módulo responde
@@ -17,7 +17,7 @@
//! //!
//! **Sin dependencias externas**: los parsers (gzip/ar/ELF) son mínimos y viven acá, en el mismo //! **Sin dependencias externas**: los parsers (gzip/ar/ELF) son mínimos y viven acá, en el mismo
//! estilo que `query::parse_elf_info`. Un diffoscope de verdad se apoya en medio mundo de binarios //! estilo que `query::parse_elf_info`. Un diffoscope de verdad se apoya en medio mundo de binarios
//! ajenos; eso es exactamente lo que hammer no puede permitirse (ADR 0004: el catálogo se construye, //! ajenos; eso es exactamente lo que takana no puede permitirse (ADR 0004: el catálogo se construye,
//! no se importa). //! no se importa).
use serde::Serialize; use serde::Serialize;
+1 -1
View File
@@ -1,4 +1,4 @@
//! Lectura de variables de entorno del proyecto durante el renombre `hammer` → `takana`. //! Lectura de variables de entorno del proyecto durante el renombre `takana` → `takana`.
//! //!
//! # Por qué existe esto en vez de un `sed` //! # Por qué existe esto en vez de un `sed`
//! //!
+1 -1
View File
@@ -1,5 +1,5 @@
//! La **base de datos de paquetes instalados** (Etapa F): qué paquetes hay puestos en un root y //! La **base de datos de paquetes instalados** (Etapa F): qué paquetes hay puestos en un root y
//! qué ficheros aportó cada uno. Es lo que hace posible `hammer uninstall`: sin un registro de //! qué ficheros aportó cada uno. Es lo que hace posible `takana uninstall`: sin un registro de
//! "este paquete creó estos ficheros", quitar un paquete sería adivinar. //! "este paquete creó estos ficheros", quitar un paquete sería adivinar.
//! //!
//! Modelo simple y honesto: `install` registra los ficheros que CREÓ (los hidratados del //! Modelo simple y honesto: `install` registra los ficheros que CREÓ (los hidratados del
+1 -1
View File
@@ -29,7 +29,7 @@ pub const CATALOG_VERSION: u32 = 1;
pub struct Catalog { pub struct Catalog {
pub version: u32, pub version: u32,
/// Kernel contra el que se revisaron las fugas. Cambiar de versión no invalida el catálogo, /// Kernel contra el que se revisaron las fugas. Cambiar de versión no invalida el catálogo,
/// pero sí obliga a re-mirar las fugas (`hammer kernel closure`). /// pero sí obliga a re-mirar las fugas (`takana kernel closure`).
#[serde(default)] #[serde(default)]
pub reviewed_against: Option<String>, pub reviewed_against: Option<String>,
#[serde(default, rename = "bundle")] #[serde(default, rename = "bundle")]
+1 -1
View File
@@ -2,7 +2,7 @@
//! callado si faltan (SDD 25 §4 y §8-W6). //! callado si faltan (SDD 25 §4 y §8-W6).
//! //!
//! ## Por qué existe este módulo //! ## Por qué existe este módulo
//! SDD 25 encontró, midiendo otra cosa, que los kernels de hammer se construyen **sin //! SDD 25 encontró, midiendo otra cosa, que los kernels de takana se construyen **sin
//! `CONFIG_MEMCG`**: `memory.max` no existe, y el escritor del otro lado (`arje-incarnate::cgroup`) //! `CONFIG_MEMCG`**: `memory.max` no existe, y el escritor del otro lado (`arje-incarnate::cgroup`)
//! descarta el error. Una Card que pide un tope de memoria arranca **sin tope** y la única huella //! descarta el error. Una Card que pide un tope de memoria arranca **sin tope** y la única huella
//! es una línea de log. Es la forma de fallo que `CLAUDE.md` §3 nombra: *un ausente falla //! es una línea de log. Es la forma de fallo que `CLAUDE.md` §3 nombra: *un ausente falla
+1 -1
View File
@@ -219,7 +219,7 @@ impl Hardware {
} }
/// La huella. BLAKE3 sobre [`Hardware::fingerprint_material`], con el mismo tipo que cualquier /// La huella. BLAKE3 sobre [`Hardware::fingerprint_material`], con el mismo tipo que cualquier
/// otro identificador por contenido de hammer. /// otro identificador por contenido de takana.
pub fn fingerprint(&self) -> ArtifactHash { pub fn fingerprint(&self) -> ArtifactHash {
ArtifactHash::of_bytes(self.fingerprint_material().as_bytes()) ArtifactHash::of_bytes(self.fingerprint_material().as_bytes())
} }
+1 -1
View File
@@ -1,6 +1,6 @@
//! Lector del grafo de Kconfig — **sólo para analizar, nunca para resolver**. //! Lector del grafo de Kconfig — **sólo para analizar, nunca para resolver**.
//! //!
//! Regla dura heredada del handoff (§6) y ratificada en `docs/22-configurador-kernel.md`: hammer //! Regla dura heredada del handoff (§6) y ratificada en `docs/22-configurador-kernel.md`: takana
//! **no** implementa un solver de Kconfig. El `.config` lo sigue produciendo el `olddefconfig` del //! **no** implementa un solver de Kconfig. El `.config` lo sigue produciendo el `olddefconfig` del
//! propio kernel. Lo que este módulo hace es *leer* los ~15 000 símbolos para poder contestar //! propio kernel. Lo que este módulo hace es *leer* los ~15 000 símbolos para poder contestar
//! preguntas sobre el grafo — «¿qué muere si apago `WIRELESS`?», «¿quién lo enciende por la //! preguntas sobre el grafo — «¿qué muere si apago `WIRELESS`?», «¿quién lo enciende por la
+1 -1
View File
@@ -12,7 +12,7 @@
//! `plan` emite una **receta derivada**, no un binario parametrizable. //! `plan` emite una **receta derivada**, no un binario parametrizable.
//! 2. Atestar «este config booteó en esta huella» sale casi gratis: ya es un artefacto CAS. //! 2. Atestar «este config booteó en esta huella» sale casi gratis: ya es un artefacto CAS.
//! //!
//! Y la regla dura que nunca se cruza: **hammer no resuelve Kconfig**. Emite fragmentos //! Y la regla dura que nunca se cruza: **takana no resuelve Kconfig**. Emite fragmentos
//! (`scripts/config -e/-d`) y deja que el `olddefconfig` del propio kernel produzca el `.config`. //! (`scripts/config -e/-d`) y deja que el `olddefconfig` del propio kernel produzca el `.config`.
//! Ver [`kconfig`]. //! Ver [`kconfig`].
+2 -2
View File
@@ -14,7 +14,7 @@
//! //!
//! ## Qué se emite y qué no //! ## Qué se emite y qué no
//! Se emiten **raíces**, no clausuras. `-d WIRELESS -d WLAN` son dos banderas; los ~400 símbolos //! Se emiten **raíces**, no clausuras. `-d WIRELESS -d WLAN` son dos banderas; los ~400 símbolos
//! que caen con ellas los calcula el `olddefconfig` **del propio kernel**. hammer sabe cuáles son //! que caen con ellas los calcula el `olddefconfig` **del propio kernel**. takana sabe cuáles son
//! (para poder explicarlos), pero no los escribe: la app nunca escribe un `.config`. //! (para poder explicarlos), pero no los escribe: la app nunca escribe un `.config`.
//! //!
//! ## Procedencia por símbolo //! ## Procedencia por símbolo
@@ -477,7 +477,7 @@ compile = "make bzImage"
assert!(p.fragment.contains("-d WIRELESS")); assert!(p.fragment.contains("-d WIRELESS"));
assert!(p.fragment.contains("-d WLAN")); assert!(p.fragment.contains("-d WLAN"));
assert!(!p.fragment.contains("CFG80211")); assert!(!p.fragment.contains("CFG80211"));
// …pero hammer SABE cuántos caen, para poder explicarlo. // …pero takana SABE cuántos caen, para poder explicarlo.
assert_eq!(p.closure_size, 4); assert_eq!(p.closure_size, 4);
} }
+2 -2
View File
@@ -1,4 +1,4 @@
//! Tipos núcleo de hammer, compartidos por el lab, la CLI y el daemon. //! Tipos núcleo de takana, compartidos por el lab, la CLI y el daemon.
//! //!
//! Ver `docs/01-architecture.md` y siguientes. Esto es el esqueleto de Fase 0: los tipos y //! Ver `docs/01-architecture.md` y siguientes. Esto es el esqueleto de Fase 0: los tipos y
//! contratos están definidos; la lógica pesada (sandbox, fanotify, bus) vive en los otros //! contratos están definidos; la lógica pesada (sandbox, fanotify, bus) vive en los otros
@@ -35,7 +35,7 @@ pub use sign::{KeyPair, SigStatus, TrustStore};
pub use store::Store; pub use store::Store;
pub use swm::{Base, BaseCompat, BaseRef, Mutation, PinDiff, Signature, Swm, SwmBuild}; pub use swm::{Base, BaseCompat, BaseRef, Mutation, PinDiff, Signature, Swm, SwmBuild};
/// Error común del ecosistema hammer. /// Error común del ecosistema takana.
#[derive(Debug, thiserror::Error)] #[derive(Debug, thiserror::Error)]
pub enum Error { pub enum Error {
#[error("io: {0}")] #[error("io: {0}")]
+1 -1
View File
@@ -13,7 +13,7 @@
//! aparezca un caso de uso real para componer queries, extenderemos sin romper este shape. //! aparezca un caso de uso real para componer queries, extenderemos sin romper este shape.
//! //!
//! Diseñado para evaluarse: //! Diseñado para evaluarse:
//! - **Local**: la CLI puede correr el evaluador en proceso (`hammer query bin:grep`). //! - **Local**: la CLI puede correr el evaluador en proceso (`takana query bin:grep`).
//! - **Remoto**: el daemon expone `Command::Query { what: "expr", expr: Some(...) }` y //! - **Remoto**: el daemon expone `Command::Query { what: "expr", expr: Some(...) }` y
//! reusa este evaluador. Útil para que un cliente sin acceso al disco (o sin caps) //! reusa este evaluador. Útil para que un cliente sin acceso al disco (o sin caps)
//! consulte estado a través del bus. //! consulte estado a través del bus.
+3 -3
View File
@@ -201,7 +201,7 @@ pub struct Build {
/// ///
/// POR QUÉ IMPORTA, medido: el **79%** del contenido binario del store son secciones `.debug_*` /// POR QUÉ IMPORTA, medido: el **79%** del contenido binario del store son secciones `.debug_*`
/// ⇒ ~96 G de reserva de disco y un espejo público de ~30 G en vez de ~126 G. Y además **cierra /// ⇒ ~96 G de reserva de disco y un espejo público de ~30 G en vez de ~126 G. Y además **cierra
/// una fuga de reproducibilidad**: `hammer why-differs` mostró que `appstream` y `bison` /// una fuga de reproducibilidad**: `takana why-differs` mostró que `appstream` y `bison`
/// divergen entre rebuilds ÚNICAMENTE en `.debug_*` (rutas internas del árbol de build), con el /// divergen entre rebuilds ÚNICAMENTE en `.debug_*` (rutas internas del árbol de build), con el
/// código ejecutable idéntico. Sin esas secciones, el artefacto reproduce. /// código ejecutable idéntico. Sin esas secciones, el artefacto reproduce.
/// ///
@@ -323,7 +323,7 @@ impl Slots {
} }
/// Evidencia de comportamiento de una receta (H1 — proof-carrying recipes, SDD 15 §H1). La IA /// Evidencia de comportamiento de una receta (H1 — proof-carrying recipes, SDD 15 §H1). La IA
/// entrega, junto al build, un conjunto de `checks` que el verificador (`hammer swm-verify /// entrega, junto al build, un conjunto de `checks` que el verificador (`takana swm-verify
/// --evidence`) corre DENTRO del sandbox reproducible; la mutación sólo se propone si todos pasan. /// --evidence`) corre DENTRO del sandbox reproducible; la mutación sólo se propone si todos pasan.
/// La confianza vive en el checker (pequeño, auditable), no en el generador: el sistema mejora solo /// La confianza vive en el checker (pequeño, auditable), no en el generador: el sistema mejora solo
/// sin poder corromperse solo. Deliberadamente FUERA de `Recipe::hash_inputs` (comportamiento ≠ /// sin poder corromperse solo. Deliberadamente FUERA de `Recipe::hash_inputs` (comportamiento ≠
@@ -505,7 +505,7 @@ impl Recipe {
toml::from_str(s).map_err(|e| crate::Error::Recipe(e.to_string())) toml::from_str(s).map_err(|e| crate::Error::Recipe(e.to_string()))
} }
/// Serializa la receta a TOML. Útil para el sidecar de provenance que `hammer-build` /// Serializa la receta a TOML. Útil para el sidecar de provenance que `takana-build`
/// escribe junto al artefacto en el store. `base_dir` no se serializa (es transient). /// escribe junto al artefacto en el store. `base_dir` no se serializa (es transient).
pub fn to_toml(&self) -> crate::Result<String> { pub fn to_toml(&self) -> crate::Result<String> {
toml::to_string(self).map_err(|e| crate::Error::Recipe(e.to_string())) toml::to_string(self).map_err(|e| crate::Error::Recipe(e.to_string()))
+3 -3
View File
@@ -1,15 +1,15 @@
//! El **repositorio de paquetes** (Etapa F): un directorio con `.swm` + un `index.json` que //! El **repositorio de paquetes** (Etapa F): un directorio con `.swm` + un `index.json` que
//! mapea `nombre → paquete`. Es el catálogo que `hammer install <nombre>` resuelve. //! mapea `nombre → paquete`. Es el catálogo que `takana install <nombre>` resuelve.
//! //!
//! Un `.swm` es un manifiesto de mutación sin identidad propia (no lleva un campo "nombre de //! Un `.swm` es un manifiesto de mutación sin identidad propia (no lleva un campo "nombre de
//! paquete"); la **identidad** (nombre+versión) la asigna el repo cuando se publica. Esto //! paquete"); la **identidad** (nombre+versión) la asigna el repo cuando se publica. Esto
//! mantiene el formato honesto (describe una transformación, no se arroga ser "el paquete X") //! mantiene el formato honesto (describe una transformación, no se arroga ser "el paquete X")
//! y deja al repo ser el namespace. `hammer pack --repo` publica con el nombre/versión de la //! y deja al repo ser el namespace. `takana pack --repo` publica con el nombre/versión de la
//! receta; `install` resuelve por nombre. //! receta; `install` resuelve por nombre.
//! //!
//! El índice es CONTENIDO PLANO (serde_json), no un binario: legible, diffeable, firmable más //! El índice es CONTENIDO PLANO (serde_json), no un binario: legible, diffeable, firmable más
//! adelante como una release. El transporte del repo (filesystem / sshfs / mirror) es ortogonal //! adelante como una release. El transporte del repo (filesystem / sshfs / mirror) es ortogonal
//! — ver `hammer-mirror` para el CAS del store; aquí el repo es simplemente un directorio. //! — ver `takana-mirror` para el CAS del store; aquí el repo es simplemente un directorio.
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
+1 -1
View File
@@ -2,7 +2,7 @@
//! //!
//! La firma cubre **el contenido del manifiesto** (`swm_version` + `base` + `mutations`), no la //! La firma cubre **el contenido del manifiesto** (`swm_version` + `base` + `mutations`), no la
//! propia firma. Sirve para autoría e integridad en tránsito — **no** sustituye a la //! propia firma. Sirve para autoría e integridad en tránsito — **no** sustituye a la
//! verificación reproducible: `hammer apply` siempre reproduce y compara, firme o no. //! verificación reproducible: `takana apply` siempre reproduce y compara, firme o no.
//! //!
//! Formato en disco (texto, una línea base64 cada uno): //! Formato en disco (texto, una línea base64 cada uno):
//! - clave privada: `<name>.ed25519` → base64(semilla de 32 bytes), modo 0600 //! - clave privada: `<name>.ed25519` → base64(semilla de 32 bytes), modo 0600
+2 -2
View File
@@ -43,12 +43,12 @@ impl Store {
/// y devolvió Ok**. Cuatro recetas dieron OK sin producir un solo fichero. /// y devolvió Ok**. Cuatro recetas dieron OK sin producir un solo fichero.
/// ///
/// El criterio es "tiene al menos una entrada", NO el sidecar `.hammer/recipe.toml`: ese lo /// El criterio es "tiene al menos una entrada", NO el sidecar `.hammer/recipe.toml`: ese lo
/// escriben los llamantes (`hammer-build`, `hammer-cli`), no `seal()`, y **`hammer-bootstrap` /// escriben los llamantes (`takana-build`, `takana-cli`), no `seal()`, y **`takana-bootstrap`
/// sella sin él** ⇒ exigirlo haría que el bootstrap se creyera nunca sellado y reconstruyera /// sella sin él** ⇒ exigirlo haría que el bootstrap se creyera nunca sellado y reconstruyera
/// siempre. Un artefacto de verdad nunca está vacío, así que esto basta para el fallo real. /// siempre. Un artefacto de verdad nunca está vacío, así que esto basta para el fallo real.
/// ///
/// Lo que esto NO cubre: una copia PARCIAL (algún fichero, no todos). Para integridad de /// Lo que esto NO cubre: una copia PARCIAL (algún fichero, no todos). Para integridad de
/// contenido están `of_tree` y `hammer attest`; esto es la caché, no la atestación. /// contenido están `of_tree` y `takana attest`; esto es la caché, no la atestación.
pub fn has(&self, h: &ArtifactHash, name: &str) -> bool { pub fn has(&self, h: &ArtifactHash, name: &str) -> bool {
let dir = self.path_of(h, name); let dir = self.path_of(h, name);
// `read_dir(..).next().is_some()`: una sola entrada basta, no recorre el árbol. // `read_dir(..).next().is_some()`: una sola entrada basta, no recorre el árbol.
+4 -4
View File
@@ -72,7 +72,7 @@ pub enum Mutation {
SourcePatch { SourcePatch {
// Origen git (modo histórico) **o** tarball — exactamente uno, igual que // Origen git (modo histórico) **o** tarball — exactamente uno, igual que
// [`crate::recipe::Source`]. Antes sólo se modelaba git y los tarballs caían a // [`crate::recipe::Source`]. Antes sólo se modelaba git y los tarballs caían a
// `file_drop`; ahora `hammer export` los reconstruye como source_patch. Los campos // `file_drop`; ahora `takana export` los reconstruye como source_patch. Los campos
// son `Option` (con `serde(default)`) para que los `.swm` git existentes —que sólo // son `Option` (con `serde(default)`) para que los `.swm` git existentes —que sólo
// traen `repo`+`commit`— sigan parseando sin cambios. // traen `repo`+`commit`— sigan parseando sin cambios.
#[serde(default, skip_serializing_if = "Option::is_none")] #[serde(default, skip_serializing_if = "Option::is_none")]
@@ -102,7 +102,7 @@ pub enum Mutation {
#[serde(default, skip_serializing_if = "crate::recipe::Deps::is_empty")] #[serde(default, skip_serializing_if = "crate::recipe::Deps::is_empty")]
deps: crate::recipe::Deps, deps: crate::recipe::Deps,
/// Evidencia de comportamiento que acompaña al paquete (H1 — proof-carrying recipes). El /// Evidencia de comportamiento que acompaña al paquete (H1 — proof-carrying recipes). El
/// receptor puede correr `hammer swm-verify --evidence` para reproducir cada check en el /// receptor puede correr `takana swm-verify --evidence` para reproducir cada check en el
/// sandbox antes de aceptar la mutación. Vacío ⇒ paquete sin evidencia (sólo reproducibilidad). /// sandbox antes de aceptar la mutación. Vacío ⇒ paquete sin evidencia (sólo reproducibilidad).
#[serde(default, skip_serializing_if = "crate::recipe::Evidence::is_empty")] #[serde(default, skip_serializing_if = "crate::recipe::Evidence::is_empty")]
evidence: crate::recipe::Evidence, evidence: crate::recipe::Evidence,
@@ -246,7 +246,7 @@ impl Swm {
/// sistema YA sabe construir se vuelve un paquete distribuible y reproducible-desde-fuente. /// sistema YA sabe construir se vuelve un paquete distribuible y reproducible-desde-fuente.
/// Es la inversa de `takana_build::swm_bridge` (que va `source_patch` → `Recipe` → build). /// Es la inversa de `takana_build::swm_bridge` (que va `source_patch` → `Recipe` → build).
/// ///
/// `hammer-core` no toca disco: el caller resuelve y **lee** los patches de la receta /// `takana-core` no toca disco: el caller resuelve y **lee** los patches de la receta
/// (relativos a `recipe.base_dir`) y pasa su texto ya concatenado en `patch_text`. El /// (relativos a `recipe.base_dir`) y pasa su texto ya concatenado en `patch_text`. El
/// `expected_hash`, si se da, ancla "verificar, no confiar" (el receptor rehace y compara). /// `expected_hash`, si se da, ancla "verificar, no confiar" (el receptor rehace y compara).
pub fn from_recipe( pub fn from_recipe(
@@ -394,7 +394,7 @@ impl Mutation {
)); ));
} }
// La evidencia (H1) viaja con el paquete: validamos su FORMA acá (cmd no vacío, // La evidencia (H1) viaja con el paquete: validamos su FORMA acá (cmd no vacío,
// hash bien formado). La EJECUCIÓN la hace `hammer swm-verify --evidence` (H1b). // hash bien formado). La EJECUCIÓN la hace `takana swm-verify --evidence` (H1b).
evidence.validate().map_err(|e| e.to_string())?; evidence.validate().map_err(|e| e.to_string())?;
Ok(()) Ok(())
} }
+1 -1
View File
@@ -5,7 +5,7 @@ edition.workspace = true
license.workspace = true license.workspace = true
authors.workspace = true authors.workspace = true
repository.workspace = true repository.workspace = true
description = "El diario de mutaciones de hammer: append-only JSON-líneas sobre eventos del FHS." description = "El diario de mutaciones de takana: append-only JSON-líneas sobre eventos del FHS."
[dependencies] [dependencies]
takana-core.workspace = true takana-core.workspace = true
+6 -6
View File
@@ -11,7 +11,7 @@
//! - El diario no bloquea acciones; sólo observa. Pero también acepta eventos "trazables" //! - El diario no bloquea acciones; sólo observa. Pero también acepta eventos "trazables"
//! (de un `hydrate` o `commit`) que llegan con `actor.source = HammerHydrate { artifact }`. //! (de un `hydrate` o `commit`) que llegan con `actor.source = HammerHydrate { artifact }`.
//! - Las "mutaciones opacas" (pisada manual sin rastro de artefacto) llegan con //! - Las "mutaciones opacas" (pisada manual sin rastro de artefacto) llegan con
//! `actor.source = External { pid, uid }`. Eso permite que `hammer export` distinga //! `actor.source = External { pid, uid }`. Eso permite que `takana export` distinga
//! replays reproducibles de cambios que sólo se pueden compartir como warnings. //! replays reproducibles de cambios que sólo se pueden compartir como warnings.
use std::io::{BufRead, BufReader, Read, Seek, SeekFrom, Write}; use std::io::{BufRead, BufReader, Read, Seek, SeekFrom, Write};
@@ -46,21 +46,21 @@ impl MutationOp {
} }
/// Quién originó la mutación. Distinguir trazable vs opaca es lo que más tarde permite /// Quién originó la mutación. Distinguir trazable vs opaca es lo que más tarde permite
/// que `hammer export` produzca un `.swm` reproducible (artefactos) más una lista de /// que `takana export` produzca un `.swm` reproducible (artefactos) más una lista de
/// warnings (pisadas manuales). /// warnings (pisadas manuales).
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "kind", rename_all = "kebab-case")] #[serde(tag = "kind", rename_all = "kebab-case")]
pub enum Source { pub enum Source {
/// Vino de un `hammer hydrate <artifact>` — la mutación es replay-able a partir del hash. /// Vino de un `takana hydrate <artifact>` — la mutación es replay-able a partir del hash.
HammerHydrate { artifact: String }, HammerHydrate { artifact: String },
/// Vino de un `hammer commit <overlay>` — origen indirecto: el `actor` fue el overlay, /// Vino de un `takana commit <overlay>` — origen indirecto: el `actor` fue el overlay,
/// y `artifact` es opcional (puede no haber, si la promoción fue de un edit manual). /// y `artifact` es opcional (puede no haber, si la promoción fue de un edit manual).
HammerCommit { HammerCommit {
overlay: String, overlay: String,
#[serde(default, skip_serializing_if = "Option::is_none")] #[serde(default, skip_serializing_if = "Option::is_none")]
artifact: Option<String>, artifact: Option<String>,
}, },
/// Pisada manual fuera del flujo de hammer. La grabamos sin juzgar — el SDD insiste en /// Pisada manual fuera del flujo de takana. La grabamos sin juzgar — el SDD insiste en
/// que es información, no error. /// que es información, no error.
External, External,
} }
@@ -246,7 +246,7 @@ impl Journal {
} }
/// Hash BLAKE3 del contenido de un archivo, con prefijo `b3:`. Es un blake3 **plano** del /// Hash BLAKE3 del contenido de un archivo, con prefijo `b3:`. Es un blake3 **plano** del
/// contenido (lo que daría `b3sum`), distinto del `ArtifactHash::of_inputs` de hammer-core /// contenido (lo que daría `b3sum`), distinto del `ArtifactHash::of_inputs` de takana-core
/// (que prefija longitudes para identidad de artefactos). Aquí queremos "¿el archivo cambió?". /// (que prefija longitudes para identidad de artefactos). Aquí queremos "¿el archivo cambió?".
pub fn content_hash_of(bytes: &[u8]) -> String { pub fn content_hash_of(bytes: &[u8]) -> String {
format!("b3:{}", blake3::hash(bytes).to_hex()) format!("b3:{}", blake3::hash(bytes).to_hex())
+1 -1
View File
@@ -1,6 +1,6 @@
//! **E3 — mirror del store content-addressed** ([SDD 13 §E3](../../docs/13-release-engineering.md)). //! **E3 — mirror del store content-addressed** ([SDD 13 §E3](../../docs/13-release-engineering.md)).
//! //!
//! El `/store` de hammer es un CAS: cada artefacto vive en un directorio `<hash>-<name>` y *el hash es //! El `/store` de takana es un CAS: cada artefacto vive en un directorio `<hash>-<name>` y *el hash es
//! la dirección*. Un mirror, entonces, es ese CAS **replicado** entre dos máquinas + **resolución por //! la dirección*. Un mirror, entonces, es ese CAS **replicado** entre dos máquinas + **resolución por
//! hash**. Esta capa replica los artefactos que le faltan al destino y **ancla la integridad en //! hash**. Esta capa replica los artefactos que le faltan al destino y **ancla la integridad en
//! `of_tree`** (el content-hash BLAKE3 del árbol, [`ArtifactHash::of_tree`]): el receptor **recomputa** //! `of_tree`** (el content-hash BLAKE3 del árbol, [`ArtifactHash::of_tree`]): el receptor **recomputa**
+1 -1
View File
@@ -5,7 +5,7 @@ edition.workspace = true
license.workspace = true license.workspace = true
authors.workspace = true authors.workspace = true
repository.workspace = true repository.workspace = true
description = "El overlay de experimentación de hammer: try/commit/discard/status sobre overlayfs." description = "El overlay de experimentación de takana: try/commit/discard/status sobre overlayfs."
[dependencies] [dependencies]
takana-core.workspace = true takana-core.workspace = true
+1 -1
View File
@@ -14,7 +14,7 @@
//! //!
//! Diseño: la librería no asume privilegios — emite los comandos `mount`/`umount` y deja //! Diseño: la librería no asume privilegios — emite los comandos `mount`/`umount` y deja
//! que el proceso que la invoca tenga (o no) CAP_SYS_ADMIN. Esto permite: //! que el proceso que la invoca tenga (o no) CAP_SYS_ADMIN. Esto permite:
//! - El CLI `hammer try` lo invoca con sudo (o setcap) en una máquina real. //! - El CLI `takana try` lo invoca con sudo (o setcap) en una máquina real.
//! - `hammerd` lo invoca desde su contexto privilegiado (Fase 5). //! - `hammerd` lo invoca desde su contexto privilegiado (Fase 5).
//! - Los tests lo ejercitan dentro de `bwrap --unshare-user-try --unshare-pid` que da //! - Los tests lo ejercitan dentro de `bwrap --unshare-user-try --unshare-pid` que da
//! CAP_SYS_ADMIN en un user-ns aislado. //! CAP_SYS_ADMIN en un user-ns aislado.
+2 -2
View File
@@ -1,11 +1,11 @@
//! `hammer-recover` — auto-recuperación de upgrades al **arranque** (E4 / refinamiento #4b). //! `hammer-recover` — auto-recuperación de upgrades al **arranque** (E4 / refinamiento #4b).
//! //!
//! El producto NO lleva el CLI `hammer` completo; este mini-binario (estático musl) es lo único que el //! El producto NO lleva el CLI `takana` completo; este mini-binario (estático musl) es lo único que el
//! sistema instalado necesita para auto-sanar un upgrade interrumpido. El wrapper `/sbin/init` lo corre //! sistema instalado necesita para auto-sanar un upgrade interrumpido. El wrapper `/sbin/init` lo corre
//! **tras montar `/store` y `/var/lib/hammer`** y **antes** de incarnar arje-zero: //! **tras montar `/store` y `/var/lib/hammer`** y **antes** de incarnar arje-zero:
//! //!
//! - Sin intento pendiente ⇒ no-op (el caso normal). //! - Sin intento pendiente ⇒ no-op (el caso normal).
//! - Con un `pending.json` (un `hammer upgrade apply` cortado por un apagón/reinicio): intenta //! - Con un `pending.json` (un `takana upgrade apply` cortado por un apagón/reinicio): intenta
//! **completar** (roll-forward, re-ejecuta el plan idempotente + commitea); si no puede (p.ej. el árbol //! **completar** (roll-forward, re-ejecuta el plan idempotente + commitea); si no puede (p.ej. el árbol
//! ya no está en `/store`), **deshace** (roll-back) para dejar el FHS consistente. En cualquier caso el //! ya no está en `/store`), **deshace** (roll-back) para dejar el FHS consistente. En cualquier caso el
//! sistema arranca en un estado coherente, no a medias. //! sistema arranca en un estado coherente, no a medias.
+1 -1
View File
@@ -5,7 +5,7 @@ edition.workspace = true
license.workspace = true license.workspace = true
authors.workspace = true authors.workspace = true
repository.workspace = true repository.workspace = true
description = "Upgrades atómicos de hammer (E4): aplica un árbol Stage1/producto del store al root vivo con generaciones y rollback." description = "Upgrades atómicos de takana (E4): aplica un árbol Stage1/producto del store al root vivo con generaciones y rollback."
[dependencies] [dependencies]
takana-core.workspace = true takana-core.workspace = true
+9 -9
View File
@@ -1,18 +1,18 @@
//! **Arranque por grafo** ([ADR 0010](../../docs/adr/0010-arranque-grafo-mirada.md)). //! **Arranque por grafo** ([ADR 0010](../../docs/adr/0010-arranque-grafo-mirada.md)).
//! //!
//! El menú de arranque de hammer no es una *lista* de kernels (GRUB) sino la **navegación del grafo //! El menú de arranque de takana no es una *lista* de kernels (GRUB) sino la **navegación del grafo
//! content-addressed de estados** del sistema. Este módulo sube el modelo de generaciones in-place //! content-addressed de estados** del sistema. Este módulo sube el modelo de generaciones in-place
//! ([`crate`]) a ese grafo navegable y define el **contrato de datos** con `mirada` (el compositor de //! ([`crate`]) a ese grafo navegable y define el **contrato de datos** con `mirada` (el compositor de
//! tawasuyu que dibuja el menú sobre KMS): //! tawasuyu que dibuja el menú sobre KMS):
//! //!
//! - **hammer → mirada**: [`build`] arma el grafo y [`emit`] lo escribe en //! - **takana → mirada**: [`build`] arma el grafo y [`emit`] lo escribe en
//! [`BOOT_GRAPH_PATH`] (`/run/hammer/boot-graph.json`), world-readable y regenerable. //! [`BOOT_GRAPH_PATH`] (`/run/hammer/boot-graph.json`), world-readable y regenerable.
//! - **mirada → hammer**: mirada escribe el `id` elegido en [`BOOT_SELECT_PATH`] **o** invoca //! - **mirada → takana**: mirada escribe el `id` elegido en [`BOOT_SELECT_PATH`] **o** invoca
//! `hammer boot activate <id>`; [`activate`] valida el id contra el grafo y **activa** el nodo //! `takana boot activate <id>`; [`activate`] valida el id contra el grafo y **activa** el nodo
//! (pivota la generación vía el rollback existente). //! (pivota la generación vía el rollback existente).
//! //!
//! La frontera es *un fichero + un comando*, no una API viva: mirada puede maquetar el menú contra un //! La frontera es *un fichero + un comando*, no una API viva: mirada puede maquetar el menú contra un
//! `boot-graph.json` de ejemplo sin esperar a hammer, y hammer emite el grafo sin esperar a mirada. //! `boot-graph.json` de ejemplo sin esperar a takana, y takana emite el grafo sin esperar a mirada.
//! //!
//! **Content-addressing.** El `id` de cada nodo es el `of_tree` (BLAKE3) del árbol que la generación //! **Content-addressing.** El `id` de cada nodo es el `of_tree` (BLAKE3) del árbol que la generación
//! proyectó — sin el prefijo `b3:`, para casar con el `<blake3>` del contrato. `parents` forma un DAG //! proyectó — sin el prefijo `b3:`, para casar con el `<blake3>` del contrato. `parents` forma un DAG
@@ -30,14 +30,14 @@ use crate::{
current, list, live_chain, rollback, Error, GenerationManifest, Result, RollbackReport, current, list, live_chain, rollback, Error, GenerationManifest, Result, RollbackReport,
}; };
/// Path bien conocido donde hammer publica el grafo para mirada. Regenerable, world-readable. /// Path bien conocido donde takana publica el grafo para mirada. Regenerable, world-readable.
pub const BOOT_GRAPH_PATH: &str = "/run/hammer/boot-graph.json"; pub const BOOT_GRAPH_PATH: &str = "/run/hammer/boot-graph.json";
/// Path bien conocido donde mirada deja el `id` del nodo a activar (canal de vuelta alternativo a /// Path bien conocido donde mirada deja el `id` del nodo a activar (canal de vuelta alternativo a
/// `hammer boot activate <id>`). /// `takana boot activate <id>`).
pub const BOOT_SELECT_PATH: &str = "/run/hammer/boot-select"; pub const BOOT_SELECT_PATH: &str = "/run/hammer/boot-select";
/// Qué representa un nodo del grafo de arranque. Guía cómo lo dibuja mirada y cómo lo activa hammer. /// Qué representa un nodo del grafo de arranque. Guía cómo lo dibuja mirada y cómo lo activa takana.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")] #[serde(rename_all = "kebab-case")]
pub enum NodeKind { pub enum NodeKind {
@@ -219,7 +219,7 @@ pub struct ActivateReport {
/// [`rollback`] atómico del modelo E4). /// [`rollback`] atómico del modelo E4).
/// - Si `id` es el nodo de **recuperación** ⇒ un rollback de la generación viva. /// - Si `id` es el nodo de **recuperación** ⇒ un rollback de la generación viva.
/// - Si `id` existe pero **no** está en la cadena viva (un "redo" hacia una generación huérfana) ⇒ /// - Si `id` existe pero **no** está en la cadena viva (un "redo" hacia una generación huérfana) ⇒
/// error honesto: eso requiere re-aplicar el árbol con `hammer upgrade apply`. /// error honesto: eso requiere re-aplicar el árbol con `takana upgrade apply`.
pub fn activate( pub fn activate(
target_root: &Path, target_root: &Path,
state_root: &Path, state_root: &Path,
+1 -1
View File
@@ -484,7 +484,7 @@ fn raiz_real(target_root: &Path) -> PathBuf {
/// a través de él— y medir el destino lo rechazaría por error. /// a través de él— y medir el destino lo rechazaría por error.
/// ///
/// EL FALLO QUE TAPA (medido 2026-08-30, `apply_no_escribe_fuera_del_root_por_un_symlink`): igual /// EL FALLO QUE TAPA (medido 2026-08-30, `apply_no_escribe_fuera_del_root_por_un_symlink`): igual
/// que en `hammer-build/hydrate.rs`, proyectar es `target_root.join(rel)` + escribir por ruta, así /// que en `takana-build/hydrate.rs`, proyectar es `target_root.join(rel)` + escribir por ruta, así
/// que un symlink de DIRECTORIO ya presente en el root se sigue. Una generación que deja puesto /// que un symlink de DIRECTORIO ya presente en el root se sigue. Una generación que deja puesto
/// `usr/share/pkg → /algún/lado` hacía que la siguiente escribiera `usr/share/pkg/archivo` FUERA /// `usr/share/pkg → /algún/lado` hacía que la siguiente escribiera `usr/share/pkg/archivo` FUERA
/// del root, con `ApplyReport` en verde. Con `--root /` no cambia nada (todo empieza por `/`); con /// del root, con `ApplyReport` en verde. Con `--root /` no cambia nada (todo empieza por `/`); con