takana etapa 4: los 10 crates de librería y el CLI pasan a takana-*

hammer-{core,build,bootstrap,overlay,journal,mirror,upgrade,agent,recover,cli}
→ takana-*, con sus deps de workspace, sus identificadores en el fuente y las
referencias -p de los scripts.

VERIFICADO que no mueve nada del corpus: `takana hash recipes/zlib.toml`
devuelve b3:dc363f26… , idéntico a antes del renombre. Los nombres de crate no
entran en hash_inputs, pero eso se comprueba, no se supone. 600 tests en verde.

DOS BINARIOS SE CONGELAN, y no por prolijidad:

- `hammerd` — paquete Y binario. Es componente de Stage 1 de la distro (musl,
  busybox, hammerd, arje-zero), lo supervisa arje-zero en el sistema arrancado,
  `PRESEED=hammerd` lo nombra en selfhost-verify y sus bytes anclan el baseline
  of_tree. El nombre del crate va en los símbolos ⇒ renombrarlo mueve los bytes.

- `hammer-recover` — el PAQUETE se renombra a takana-recover, el BINARIO no.
  hammer-live-install.sh lo copia a /usr/sbin/hammer-recover en sistemas ya
  instalados y hornea un hook de arranque que lo invoca por ese nombre:
  renombrarlo rompe máquinas instaladas, no el repo.

Consecuencia que hay que anotar igual: al renombrar hammer-core, los bytes de
hammerd cambian de todos modos porque linkea contra un crate con otro nombre.
El baseline of_tree del selfhost hay que rehacerlo — es efecto de la etapa 4,
no de un cambio de hammerd.

Las referencias en comentarios de recetas y docs (rutas hammer-core/src/…)
quedan para la etapa 5: son texto, no mueven hash.
This commit is contained in:
Sergio
2026-09-09 18:46:41 +00:00
parent d47cafa05d
commit 24bcf1783c
105 changed files with 943 additions and 940 deletions
Generated
+162 -162
View File
@@ -483,179 +483,21 @@ dependencies = [
"wasip3",
]
[[package]]
name = "hammer-agent"
version = "0.0.1"
dependencies = [
"anyhow",
"base64",
"hammer-core",
"hammer-journal",
"hammer-overlay",
"serde",
"serde_json",
"serde_yaml",
"tempfile",
"thiserror",
"tracing",
"ureq",
]
[[package]]
name = "hammer-bootstrap"
version = "0.0.1"
dependencies = [
"anyhow",
"hammer-build",
"hammer-core",
"hex",
"serde",
"serde_json",
"sha2",
"tempfile",
"thiserror",
"tracing",
]
[[package]]
name = "hammer-build"
version = "0.0.1"
dependencies = [
"anyhow",
"hammer-core",
"hex",
"libc",
"serde_json",
"sha2",
"tempfile",
"thiserror",
"tracing",
"tracing-subscriber",
]
[[package]]
name = "hammer-cli"
version = "0.0.1"
dependencies = [
"anyhow",
"base64",
"clap",
"hammer-agent",
"hammer-bootstrap",
"hammer-build",
"hammer-core",
"hammer-journal",
"hammer-mirror",
"hammer-overlay",
"hammer-upgrade",
"hex",
"nix",
"serde",
"serde_json",
"sha2",
"tempfile",
"toml",
"tracing",
"tracing-subscriber",
]
[[package]]
name = "hammer-core"
version = "0.0.1"
dependencies = [
"anyhow",
"base64",
"blake3",
"ed25519-dalek",
"getrandom 0.2.17",
"serde",
"serde_json",
"serde_yaml",
"tempfile",
"thiserror",
"toml",
]
[[package]]
name = "hammer-journal"
version = "0.0.1"
dependencies = [
"anyhow",
"blake3",
"hammer-core",
"serde",
"serde_json",
"tempfile",
"thiserror",
"tracing",
]
[[package]]
name = "hammer-mirror"
version = "0.0.1"
dependencies = [
"anyhow",
"hammer-core",
"serde",
"serde_json",
"tempfile",
"thiserror",
"tracing",
]
[[package]]
name = "hammer-overlay"
version = "0.0.1"
dependencies = [
"anyhow",
"hammer-core",
"hammer-journal",
"libc",
"serde",
"serde_json",
"tempfile",
"thiserror",
"tracing",
"tracing-subscriber",
]
[[package]]
name = "hammer-recover"
version = "0.0.1"
dependencies = [
"hammer-journal",
"hammer-upgrade",
]
[[package]]
name = "hammer-upgrade"
version = "0.0.1"
dependencies = [
"anyhow",
"hammer-core",
"hammer-journal",
"serde",
"serde_json",
"tempfile",
"thiserror",
"tracing",
]
[[package]]
name = "hammerd"
version = "0.0.1"
dependencies = [
"anyhow",
"clap",
"hammer-build",
"hammer-core",
"hammer-journal",
"hammer-overlay",
"libc",
"nix",
"postcard",
"serde",
"serde_json",
"takana-build",
"takana-core",
"takana-journal",
"takana-overlay",
"tempfile",
"thiserror",
"tracing",
@@ -1381,6 +1223,164 @@ dependencies = [
"syn",
]
[[package]]
name = "takana-agent"
version = "0.0.1"
dependencies = [
"anyhow",
"base64",
"serde",
"serde_json",
"serde_yaml",
"takana-core",
"takana-journal",
"takana-overlay",
"tempfile",
"thiserror",
"tracing",
"ureq",
]
[[package]]
name = "takana-bootstrap"
version = "0.0.1"
dependencies = [
"anyhow",
"hex",
"serde",
"serde_json",
"sha2",
"takana-build",
"takana-core",
"tempfile",
"thiserror",
"tracing",
]
[[package]]
name = "takana-build"
version = "0.0.1"
dependencies = [
"anyhow",
"hex",
"libc",
"serde_json",
"sha2",
"takana-core",
"tempfile",
"thiserror",
"tracing",
"tracing-subscriber",
]
[[package]]
name = "takana-cli"
version = "0.0.1"
dependencies = [
"anyhow",
"base64",
"clap",
"hex",
"nix",
"serde",
"serde_json",
"sha2",
"takana-agent",
"takana-bootstrap",
"takana-build",
"takana-core",
"takana-journal",
"takana-mirror",
"takana-overlay",
"takana-upgrade",
"tempfile",
"toml",
"tracing",
"tracing-subscriber",
]
[[package]]
name = "takana-core"
version = "0.0.1"
dependencies = [
"anyhow",
"base64",
"blake3",
"ed25519-dalek",
"getrandom 0.2.17",
"serde",
"serde_json",
"serde_yaml",
"tempfile",
"thiserror",
"toml",
]
[[package]]
name = "takana-journal"
version = "0.0.1"
dependencies = [
"anyhow",
"blake3",
"serde",
"serde_json",
"takana-core",
"tempfile",
"thiserror",
"tracing",
]
[[package]]
name = "takana-mirror"
version = "0.0.1"
dependencies = [
"anyhow",
"serde",
"serde_json",
"takana-core",
"tempfile",
"thiserror",
"tracing",
]
[[package]]
name = "takana-overlay"
version = "0.0.1"
dependencies = [
"anyhow",
"libc",
"serde",
"serde_json",
"takana-core",
"takana-journal",
"tempfile",
"thiserror",
"tracing",
"tracing-subscriber",
]
[[package]]
name = "takana-recover"
version = "0.0.1"
dependencies = [
"takana-journal",
"takana-upgrade",
]
[[package]]
name = "takana-upgrade"
version = "0.0.1"
dependencies = [
"anyhow",
"serde",
"serde_json",
"takana-core",
"takana-journal",
"tempfile",
"thiserror",
"tracing",
]
[[package]]
name = "tempfile"
version = "3.27.0"
+18 -18
View File
@@ -1,16 +1,16 @@
[workspace]
resolver = "2"
members = [
"crates/hammer-core",
"crates/hammer-build",
"crates/hammer-bootstrap",
"crates/hammer-overlay",
"crates/hammer-journal",
"crates/hammer-mirror",
"crates/hammer-upgrade",
"crates/hammer-recover",
"crates/hammer-agent",
"crates/hammer-cli",
"crates/takana-core",
"crates/takana-build",
"crates/takana-bootstrap",
"crates/takana-overlay",
"crates/takana-journal",
"crates/takana-mirror",
"crates/takana-upgrade",
"crates/takana-recover",
"crates/takana-agent",
"crates/takana-cli",
"crates/hammerd",
"crates/netup",
]
@@ -23,14 +23,14 @@ authors = ["sergio <gerencia@jlsoltech.com>"]
repository = "https://gitea.gioser.net/sergio/hammer"
[workspace.dependencies]
hammer-core = { path = "crates/hammer-core" }
hammer-build = { path = "crates/hammer-build" }
hammer-bootstrap = { path = "crates/hammer-bootstrap" }
hammer-overlay = { path = "crates/hammer-overlay" }
hammer-journal = { path = "crates/hammer-journal" }
hammer-mirror = { path = "crates/hammer-mirror" }
hammer-upgrade = { path = "crates/hammer-upgrade" }
hammer-agent = { path = "crates/hammer-agent" }
takana-core = { path = "crates/takana-core" }
takana-build = { path = "crates/takana-build" }
takana-bootstrap = { path = "crates/takana-bootstrap" }
takana-overlay = { path = "crates/takana-overlay" }
takana-journal = { path = "crates/takana-journal" }
takana-mirror = { path = "crates/takana-mirror" }
takana-upgrade = { path = "crates/takana-upgrade" }
takana-agent = { path = "crates/takana-agent" }
# shared third-party deps, pinned at the workspace level
anyhow = "1"
+4 -4
View File
@@ -12,10 +12,10 @@ name = "hammerd"
path = "src/main.rs"
[dependencies]
hammer-core.workspace = true
hammer-build.workspace = true
hammer-journal.workspace = true
hammer-overlay.workspace = true
takana-core.workspace = true
takana-build.workspace = true
takana-journal.workspace = true
takana-overlay.workspace = true
anyhow.workspace = true
clap.workspace = true
thiserror.workspace = true
+26 -26
View File
@@ -25,7 +25,7 @@ use std::thread;
use nix::sys::socket::{getsockopt, sockopt::PeerCredentials};
use crate::events::EventBus;
use hammer_core::proto::{
use takana_core::proto::{
Cap, Command, Event, EvidenceCheckVerdict, EvidenceVerdict, Peer, RecipeInline,
PROTOCOL_VERSION,
};
@@ -49,7 +49,7 @@ pub fn default_policy() -> CapsPolicy {
/// Política derivada de un fichero `agent-caps.toml` (SDD 07 §4). El peer no elige sus caps;
/// las resuelve la config por `(uid, gid)`.
pub fn policy_from_config(cfg: hammer_core::AgentCapsConfig) -> CapsPolicy {
pub fn policy_from_config(cfg: takana_core::AgentCapsConfig) -> CapsPolicy {
Arc::new(move |peer: &Peer| cfg.caps_for(peer.uid, peer.gid))
}
@@ -259,7 +259,7 @@ fn dispatch(
.ok();
}
Command::Inject { artifact, target, overlay: _ } => {
let store = match hammer_core::Store::open(&ctx.store_root) {
let store = match takana_core::Store::open(&ctx.store_root) {
Ok(s) => s,
Err(e) => {
let _ = tx.send(Event::Error {
@@ -279,10 +279,10 @@ fn dispatch(
return;
}
};
let report = match hammer_build::run_hydrate(
let report = match takana_build::run_hydrate(
&artifact_dir,
Path::new(&target),
hammer_core::LinkMode::Static,
takana_core::LinkMode::Static,
None,
) {
Ok(r) => r,
@@ -333,7 +333,7 @@ fn dispatch(
// El daemon evalúa con el `fs_root` del sistema host (None ⇒ rutas
// absolutas tal y como vienen). Si el .swm necesitara otra base, el
// cliente puede evaluar localmente con un EvalContext distinto.
hammer_core::query::eval_str(e, &hammer_core::query::EvalContext::new())
takana_core::query::eval_str(e, &takana_core::query::EvalContext::new())
}
other => {
let _ = tx.send(Event::Error {
@@ -363,7 +363,7 @@ fn dispatch(
fn run_compile(recipe: RecipeInline, store_root: PathBuf, tx: Sender<Event>) {
let name = recipe.name.clone();
let mutation = hammer_core::swm::Mutation::SourcePatch {
let mutation = takana_core::swm::Mutation::SourcePatch {
repo: recipe.repo,
commit: recipe.commit,
tarball: recipe.tarball,
@@ -371,7 +371,7 @@ fn run_compile(recipe: RecipeInline, store_root: PathBuf, tx: Sender<Event>) {
strip_components: None,
patch: recipe.patch,
patch_url: None,
build: hammer_core::swm::SwmBuild {
build: takana_core::swm::SwmBuild {
compiler: recipe.compiler,
target: recipe.target,
link: recipe.link,
@@ -389,7 +389,7 @@ fn run_compile(recipe: RecipeInline, store_root: PathBuf, tx: Sender<Event>) {
// de compatibilidad vive en `install` (CLI) contra el estado instalado. Vacío aquí.
slots: Default::default(),
};
let store = match hammer_core::Store::open(&store_root) {
let store = match takana_core::Store::open(&store_root) {
Ok(s) => s,
Err(e) => {
let _ = tx.send(Event::BuildFailed {
@@ -400,8 +400,8 @@ fn run_compile(recipe: RecipeInline, store_root: PathBuf, tx: Sender<Event>) {
return;
}
};
let cfg = hammer_build::BuildConfig::from_env_or_defaults(store.root());
match hammer_build::build_source_patch(&mutation, &cfg, &store, None, None) {
let cfg = takana_build::BuildConfig::from_env_or_defaults(store.root());
match takana_build::build_source_patch(&mutation, &cfg, &store, None, None) {
Ok(h) => {
// [H1c] El artefacto está sellado; ahora EJECUTAMOS la evidencia declarada y
// adjuntamos el veredicto. El gate ("no proponer" si falla) vive en el orquestador
@@ -417,7 +417,7 @@ fn run_compile(recipe: RecipeInline, store_root: PathBuf, tx: Sender<Event>) {
Err(e) => {
// Si el fallo trae la cola del log del lab, la separamos de la razón para que la
// IA pueda leer el error real del compilador, no sólo el mensaje de Rust.
let (reason, log_tail) = match hammer_build::sandbox::BuildFailure::from_error(&e) {
let (reason, log_tail) = match takana_build::sandbox::BuildFailure::from_error(&e) {
Some(bf) => (bf.reason.clone(), bf.log_tail.clone()),
None => (e.to_string(), None),
};
@@ -437,13 +437,13 @@ fn run_compile(recipe: RecipeInline, store_root: PathBuf, tx: Sender<Event>) {
/// - `Some(all_passed=false, …)` sintético si la evidencia ni siquiera pudo ejecutarse (falta una
/// build-dep, error de sandbox…): eso también debe bloquear la propuesta, no pasar en silencio.
fn evidence_verdict(
mutation: &hammer_core::swm::Mutation,
cfg: &hammer_build::BuildConfig,
store: &hammer_core::Store,
mutation: &takana_core::swm::Mutation,
cfg: &takana_build::BuildConfig,
store: &takana_core::Store,
) -> Option<EvidenceVerdict> {
let has_evidence = matches!(
mutation,
hammer_core::swm::Mutation::SourcePatch { evidence, .. } if !evidence.is_empty()
takana_core::swm::Mutation::SourcePatch { evidence, .. } if !evidence.is_empty()
);
if !has_evidence {
return None;
@@ -456,7 +456,7 @@ fn evidence_verdict(
all_passed: false,
max_level: None,
checks: vec![EvidenceCheckVerdict {
kind: hammer_core::EvidenceKind::CmdExit,
kind: takana_core::EvidenceKind::CmdExit,
cmd: "<runner>".into(),
passed: false,
detail: format!("la evidencia no pudo ejecutarse: {e}"),
@@ -467,14 +467,14 @@ fn evidence_verdict(
}
fn run_evidence_inner(
mutation: &hammer_core::swm::Mutation,
cfg: &hammer_build::BuildConfig,
store: &hammer_core::Store,
) -> hammer_core::Result<EvidenceVerdict> {
let catalog_dir = hammer_build::swm_bridge::catalog_dir_for(cfg, None);
mutation: &takana_core::swm::Mutation,
cfg: &takana_build::BuildConfig,
store: &takana_core::Store,
) -> takana_core::Result<EvidenceVerdict> {
let catalog_dir = takana_build::swm_bridge::catalog_dir_for(cfg, None);
std::fs::create_dir_all(&catalog_dir)?;
let recipe = hammer_build::swm_bridge::recipe_from_source_patch(mutation, None, &catalog_dir)?;
let report = hammer_build::run_evidence(&recipe, cfg, store)?;
let recipe = takana_build::swm_bridge::recipe_from_source_patch(mutation, None, &catalog_dir)?;
let report = takana_build::run_evidence(&recipe, cfg, store)?;
Ok(EvidenceVerdict {
all_passed: report.all_passed(),
max_level: report.max_level_passed(),
@@ -510,7 +510,7 @@ fn query_file(path: &str) -> serde_json::Value {
}
fn query_artifact(store_root: &Path, hash: &str) -> serde_json::Value {
let store = match hammer_core::Store::open(store_root) {
let store = match takana_core::Store::open(store_root) {
Ok(s) => s,
Err(e) => return serde_json::json!({"error": e.to_string()}),
};
@@ -552,7 +552,7 @@ mod tests {
#[test]
fn config_policy_resolves_by_uid_gid() {
let cfg = hammer_core::AgentCapsConfig::from_toml(
let cfg = takana_core::AgentCapsConfig::from_toml(
r#"
default = ["query"]
[[rule]]
+1 -1
View File
@@ -28,7 +28,7 @@
use std::sync::mpsc::Receiver;
use hammer_core::proto::Event;
use takana_core::proto::Event;
use crate::events::EventBus;
+2 -2
View File
@@ -12,7 +12,7 @@
use std::sync::mpsc::{self, Receiver, Sender};
use std::sync::{Arc, Mutex};
use hammer_core::proto::Event;
use takana_core::proto::Event;
#[derive(Clone)]
pub struct EventBus {
@@ -58,7 +58,7 @@ impl EventBus {
#[cfg(test)]
mod tests {
use super::*;
use hammer_core::proto::Event;
use takana_core::proto::Event;
#[test]
fn publish_reaches_all_subs() {
+3 -3
View File
@@ -33,7 +33,7 @@ struct Args {
journal: String,
/// Directorio raíz de overlays (para que el watcher pueda filtrar eventos bajo overlays
/// activos). Default coincide con `hammer try`.
#[arg(long, default_value = hammer_overlay::DEFAULT_STATE_ROOT)]
#[arg(long, default_value = takana_overlay::DEFAULT_STATE_ROOT)]
overlay_state_root: String,
/// Directorios extra a vigilar; si vacío, usa los defaults del FHS (SDD 05 §2).
#[arg(long = "watch")]
@@ -97,7 +97,7 @@ fn main() -> anyhow::Result<()> {
// Watcher fanotify: opcional. Si falla (no caps), el daemon sigue con el bus solo.
if !args.no_watcher {
let journal = hammer_journal::Journal::open(&args.journal)?;
let journal = takana_journal::Journal::open(&args.journal)?;
let mut dirs = watcher::default_watch_dirs();
dirs.extend(args.extra_watch.clone());
let overlay_root = PathBuf::from(&args.overlay_state_root);
@@ -132,7 +132,7 @@ fn main() -> anyhow::Result<()> {
// Bus de agente: bloqueante en este thread. Si falla, fin del daemon.
// Política de caps: del fichero si existe, si no la heurística por-UID por defecto.
let policy = match hammer_core::AgentCapsConfig::load(&args.agent_caps) {
let policy = match takana_core::AgentCapsConfig::load(&args.agent_caps) {
Ok(Some(cfg)) => {
tracing::info!(path = %args.agent_caps.display(), rules = cfg.rule.len(),
"bus: política de caps cargada de fichero");
+8 -8
View File
@@ -6,7 +6,7 @@
//! * Marcamos como `FAN_MARK_ADD | FAN_MARK_ONLYDIR` cada directorio vigilado. La cobertura
//! por defecto (ver `default_watch_dirs`) cubre el FHS clásico de userland.
//! * Filtro mínimo: ignoramos eventos cuya ruta resuelta cae dentro de un overlay activo
//! (consultamos `hammer_overlay::status` en cada batch — para Fase 3 el coste es
//! (consultamos `takana_overlay::status` en cada batch — para Fase 3 el coste es
//! despreciable; cuando los overlays se vuelvan numerosos cachearemos los paths).
//! * 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
@@ -27,7 +27,7 @@ use std::os::fd::AsFd;
use std::os::unix::ffi::OsStringExt;
use std::path::{Path, PathBuf};
use hammer_journal::{Actor, Journal, MutationEvent, MutationOp, Source};
use takana_journal::{Actor, Journal, MutationEvent, MutationOp, Source};
use nix::fcntl::AT_FDCWD;
use nix::sys::fanotify::{EventFFlags, Fanotify, InitFlags, MarkFlags, MaskFlags};
@@ -48,7 +48,7 @@ pub enum Error {
#[error("read_events: {0}")]
Read(nix::errno::Errno),
#[error("journal: {0}")]
Journal(#[from] hammer_journal::Error),
Journal(#[from] takana_journal::Error),
#[error("io: {0}")]
Io(#[from] std::io::Error),
}
@@ -160,7 +160,7 @@ impl Watcher {
let content_hash = if deleted {
None
} else {
match hammer_journal::hash_file(&path) {
match takana_journal::hash_file(&path) {
Ok(h) => h,
Err(e) => {
tracing::warn!(path = %path.display(), error = %e, "watcher: no pude hashear contenido");
@@ -170,7 +170,7 @@ impl Watcher {
};
let op = self.classify_op(&path, deleted)?;
let me = MutationEvent {
ts: hammer_journal::now_rfc3339(),
ts: takana_journal::now_rfc3339(),
op,
path,
by: Actor {
@@ -188,7 +188,7 @@ impl Watcher {
}
recorded += 1;
if let Some(bus) = &self.events {
bus.publish(&hammer_core::proto::Event::Modified {
bus.publish(&takana_core::proto::Event::Modified {
path: me.path.display().to_string(),
op: me.op.as_str().to_string(),
ts: me.ts.clone(),
@@ -216,7 +216,7 @@ impl Watcher {
let Some(root) = &self.overlay_state_root else {
return Vec::new();
};
match hammer_overlay::status(root) {
match takana_overlay::status(root) {
Ok(overlays) => overlays
.into_iter()
.flat_map(|o| o.mounts.into_iter().map(|m| m.target))
@@ -301,7 +301,7 @@ mod tests {
fn me(op: MutationOp, path: &str, hash: Option<&str>) -> MutationEvent {
MutationEvent {
ts: hammer_journal::now_rfc3339(),
ts: takana_journal::now_rfc3339(),
op,
path: PathBuf::from(path),
by: Actor { source: Source::External, pid: Some(1), uid: Some(0) },
+1 -1
View File
@@ -24,7 +24,7 @@ use std::path::PathBuf;
use std::thread;
use std::time::Duration;
use hammer_core::proto::Event;
use takana_core::proto::Event;
use serde::Serialize;
use ulid::Ulid;
+1 -1
View File
@@ -23,7 +23,7 @@ use std::sync::Arc;
use std::thread;
use std::time::Duration;
use hammer_core::proto::{Cap, Command, Event, Peer, RecipeInline};
use takana_core::proto::{Cap, Command, Event, Peer, RecipeInline};
/// Helper: lanza un bus en background y devuelve la ruta del socket. Limpia al final del
/// test gracias al `tempfile::TempDir` que se le pasa al caller.
@@ -1,5 +1,5 @@
[package]
name = "hammer-agent"
name = "takana-agent"
version.workspace = true
edition.workspace = true
license.workspace = true
@@ -14,8 +14,8 @@ default = []
llm-claude = ["dep:ureq"]
[dependencies]
hammer-core.workspace = true
hammer-overlay.workspace = true
takana-core.workspace = true
takana-overlay.workspace = true
anyhow.workspace = true
thiserror.workspace = true
serde.workspace = true
@@ -26,5 +26,5 @@ ureq = { version = "2", optional = true, default-features = false, features = ["
[dev-dependencies]
tempfile.workspace = true
hammer-journal.workspace = true
takana-journal.workspace = true
base64.workspace = true
@@ -17,7 +17,7 @@ use std::sync::{Arc, Condvar, Mutex};
use std::thread;
use std::time::{Duration, Instant};
use hammer_core::proto::{Cap, Command, Event, Peer, RecipeInline};
use takana_core::proto::{Cap, Command, Event, Peer, RecipeInline};
#[derive(Debug, thiserror::Error)]
pub enum ClientError {
@@ -49,7 +49,7 @@ pub struct Welcome {
#[derive(Debug, Clone)]
pub struct CompileOutcome {
pub artifact: String,
pub verdict: Option<hammer_core::proto::EvidenceVerdict>,
pub verdict: Option<takana_core::proto::EvidenceVerdict>,
}
/// Estado compartido entre el reader thread y el caller.
@@ -111,7 +111,7 @@ impl AgentClient {
},
};
me.send(&Command::Hello {
ver: hammer_core::proto::PROTOCOL_VERSION,
ver: takana_core::proto::PROTOCOL_VERSION,
client: client.to_string(),
})?;
let ev = me.recv_pending(Duration::from_secs(5))?;
@@ -182,7 +182,7 @@ impl AgentClient {
}
/// Evalúa una expresión del mini-lenguaje (`bin:grep`, `pin:musl`, …) en el daemon.
/// El JSON devuelto sigue el schema de `hammer_core::query::eval`.
/// El JSON devuelto sigue el schema de `takana_core::query::eval`.
pub fn query_expr(&mut self, expr: &str, timeout: Duration) -> Result<serde_json::Value> {
self.send(&Command::Query {
what: "expr".into(),
@@ -38,11 +38,11 @@ pub enum Error {
#[error("io: {0}")]
Io(#[from] std::io::Error),
#[error("overlay: {0}")]
Overlay(#[from] hammer_overlay::Error),
Overlay(#[from] takana_overlay::Error),
#[error("apply: {0}")]
Apply(#[from] hammer_core::apply::ApplyError),
Apply(#[from] takana_core::apply::ApplyError),
#[error("core: {0}")]
Core(#[from] hammer_core::Error),
Core(#[from] takana_core::Error),
}
pub type Result<T> = std::result::Result<T, Error>;
@@ -7,7 +7,7 @@
//! schema → swm.verify_schema()
//! base → swm.verify_base(local) → debe ser Ok
//! build → para cada source_patch: AgentClient.compile() vía el bus (o saltar si --no-bus)
//! try → hammer_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
//! verify → spot-checks post-condición sobre disco
//! propose → devuelve Proposal con overlay_id + log de checks
@@ -18,11 +18,11 @@
use std::path::{Path, PathBuf};
use std::time::Duration;
use hammer_core::apply::{apply_config_edit, apply_file_drop, rebase_path};
use hammer_core::proto::RecipeInline;
use hammer_core::swm::{Mutation, Swm};
use hammer_core::BaseRef;
use hammer_overlay::OverlayId;
use takana_core::apply::{apply_config_edit, apply_file_drop, rebase_path};
use takana_core::proto::RecipeInline;
use takana_core::swm::{Mutation, Swm};
use takana_core::BaseRef;
use takana_overlay::OverlayId;
use crate::client::AgentClient;
use crate::translator::{IntentTranslator, SystemContext};
@@ -79,7 +79,7 @@ impl<T: IntentTranslator> Orchestrator<T> {
// 3. BASE
match swm.verify_base(&self.base) {
hammer_core::BaseCompat::Ok => {}
takana_core::BaseCompat::Ok => {}
other => {
return Err(Error::Orchestrate(format!(
"base incompatible: {other:?}"
@@ -91,7 +91,7 @@ impl<T: IntentTranslator> Orchestrator<T> {
let (overlay_id, prefix): (Option<OverlayId>, Option<PathBuf>) = match &self.apply {
ApplyTarget::Overlay { state_root } => {
std::fs::create_dir_all(state_root)?;
let id = hammer_overlay::try_overlay(&[], state_root)?;
let id = takana_overlay::try_overlay(&[], state_root)?;
(Some(id), None)
}
ApplyTarget::Prefix { prefix } => {
@@ -147,8 +147,8 @@ impl<T: IntentTranslator> Orchestrator<T> {
}
Mutation::InitRule { action, service, command } => {
let rules_dir =
rebase_path(hammer_core::apply::INIT_RULES_DIR, prefix.as_deref());
let p = hammer_core::apply::apply_init_rule(
rebase_path(takana_core::apply::INIT_RULES_DIR, prefix.as_deref());
let p = takana_core::apply::apply_init_rule(
&rules_dir, action, service, command,
)
.map_err(|e| Error::Orchestrate(format!("init_rule '{action} {service}': {e}")))?;
@@ -246,7 +246,7 @@ impl<T: IntentTranslator> Orchestrator<T> {
// Hidratamos localmente desde el store (sin pasar por el bus, evita un
// segundo Inject que duplicaría trabajo).
let store = hammer_core::Store::open(store)?;
let store = takana_core::Store::open(store)?;
let artifact_dir = store
.find_by_hash(&artifact)
.map_err(|e| Error::Orchestrate(format!("find_by_hash: {e}")))?;
@@ -340,13 +340,13 @@ fn wait_for_crash(policy: &RepairPolicy) -> Option<CrashInfo> {
let deadline = std::time::Instant::now() + policy.crash_window;
// Drenamos primero la cola asíncrona ya acumulada (eventos llegados durante el `run`).
for ev in client.drain_async() {
if let hammer_core::proto::Event::Crashed { service, code } = ev {
if let takana_core::proto::Event::Crashed { service, code } = ev {
return Some(CrashInfo { service, code });
}
}
while let Some(remaining) = deadline.checked_duration_since(std::time::Instant::now()) {
match client.next_async(remaining) {
Ok(hammer_core::proto::Event::Crashed { service, code }) => {
Ok(takana_core::proto::Event::Crashed { service, code }) => {
return Some(CrashInfo { service, code });
}
Ok(_other) => continue,
@@ -554,7 +554,7 @@ impl Default for RepairPolicy {
mod tests {
use super::*;
use crate::translator::MockTranslator;
use hammer_core::swm::{Base, Mutation};
use takana_core::swm::{Base, Mutation};
use std::collections::BTreeMap;
fn baseref() -> BaseRef {
@@ -626,7 +626,7 @@ mod tests {
strip_components: None,
patch: None,
patch_url: None,
build: hammer_core::swm::SwmBuild {
build: takana_core::swm::SwmBuild {
compiler: "zig-cc".into(),
target: "x86_64-linux-musl".into(),
link: "static".into(),
@@ -12,7 +12,7 @@
use std::collections::HashMap;
use std::path::Path;
use hammer_core::{BaseRef, Swm};
use takana_core::{BaseRef, Swm};
#[derive(Debug, thiserror::Error)]
pub enum TranslateError {
@@ -165,7 +165,7 @@ impl IntentTranslator for MockTranslator {
#[cfg(test)]
mod tests {
use super::*;
use hammer_core::swm::{Base, Mutation};
use takana_core::swm::{Base, Mutation};
use std::collections::BTreeMap;
fn empty_ctx() -> SystemContext {
@@ -31,7 +31,7 @@
//! ```
use crate::translator::{IntentTranslator, SystemContext, TranslateError};
use hammer_core::Swm;
use takana_core::Swm;
/// Inyección HTTP. La implementación por defecto ([`UreqHttp`]) usa `ureq` síncrono;
/// los tests pasan una fake.
@@ -279,7 +279,7 @@ mod tests {
use std::collections::BTreeMap;
use std::sync::{Arc, Mutex};
use hammer_core::BaseRef;
use takana_core::BaseRef;
/// Fake HTTP que captura el request y devuelve un body pre-armado.
struct FakeHttp {
@@ -11,12 +11,12 @@
use std::collections::BTreeMap;
use base64::{engine::general_purpose::STANDARD, Engine as _};
use hammer_agent::{
use takana_agent::{
orchestrator::{ApplyTarget, CompileMode},
MockTranslator, Orchestrator,
};
use hammer_core::swm::{Base, Mutation, Swm};
use hammer_core::{ArtifactHash, BaseRef};
use takana_core::swm::{Base, Mutation, Swm};
use takana_core::{ArtifactHash, BaseRef};
fn baseref() -> BaseRef {
let mut pins = BTreeMap::new();
@@ -86,7 +86,7 @@ fn intent_to_overlay_proposal_via_mock_translator() {
proposal
.checks
.iter()
.all(|c| matches!(c.level, hammer_agent::orchestrator::CheckLevel::Pass)),
.all(|c| matches!(c.level, takana_agent::orchestrator::CheckLevel::Pass)),
"todas las checks deben pasar"
);
@@ -10,7 +10,7 @@ use std::path::PathBuf;
use std::thread;
use std::time::Duration;
use hammer_core::proto::{Cap, Command, Event, Peer, RecipeInline, PROTOCOL_VERSION};
use takana_core::proto::{Cap, Command, Event, Peer, RecipeInline, PROTOCOL_VERSION};
fn start_stub_bus(sock: PathBuf) -> std::sync::mpsc::Sender<Event> {
let (tx_async, rx_async) = std::sync::mpsc::channel::<Event>();
@@ -86,14 +86,14 @@ fn stub_serve(stream: UnixStream, rx_async: std::sync::mpsc::Receiver<Event>) {
// El stub refleja la evidencia declarada como un veredicto "todo pasa" para
// ejercitar el camino H1c del cliente (sin lab real que la ejecute).
let verdict = (!recipe.evidence.is_empty()).then(|| {
hammer_core::proto::EvidenceVerdict {
takana_core::proto::EvidenceVerdict {
all_passed: true,
max_level: recipe.evidence.checks.iter().map(|c| c.kind).max(),
checks: recipe
.evidence
.checks
.iter()
.map(|c| hammer_core::proto::EvidenceCheckVerdict {
.map(|c| takana_core::proto::EvidenceCheckVerdict {
kind: c.kind,
cmd: c.cmd.clone(),
passed: true,
@@ -152,7 +152,7 @@ fn client_handshake_compile_and_async_modified() {
thread::sleep(Duration::from_millis(10));
}
let mut client = hammer_agent::AgentClient::connect(&sock).expect("connect");
let mut client = takana_agent::AgentClient::connect(&sock).expect("connect");
assert_eq!(client.welcome.ver, PROTOCOL_VERSION);
assert!(client.welcome.caps.contains(&Cap::Compile));
@@ -167,9 +167,9 @@ fn client_handshake_compile_and_async_modified() {
target: "x86_64-linux-musl".into(),
link: "static".into(),
flags: vec![],
evidence: hammer_core::Evidence {
checks: vec![hammer_core::EvidenceCheck {
kind: hammer_core::EvidenceKind::CmdExit,
evidence: takana_core::Evidence {
checks: vec![takana_core::EvidenceCheck {
kind: takana_core::EvidenceKind::CmdExit,
cmd: "grep --version".into(),
expected_exit: 0,
expected_output: None,
@@ -184,7 +184,7 @@ fn client_handshake_compile_and_async_modified() {
let verdict = outcome.verdict.expect("verdict presente (declaramos evidencia)");
assert!(verdict.all_passed);
assert_eq!(verdict.checks.len(), 1);
assert_eq!(verdict.max_level, Some(hammer_core::EvidenceKind::CmdExit));
assert_eq!(verdict.max_level, Some(takana_core::EvidenceKind::CmdExit));
// El stub también puede empujar eventos asíncronos. Inyectamos un Modified y vemos
// que el cliente lo entrega por drain_async()/next_async().
@@ -10,13 +10,13 @@ use std::path::PathBuf;
use std::thread;
use std::time::Duration;
use hammer_agent::orchestrator::{ApplyTarget, CompileMode, Orchestrator};
use hammer_agent::MockTranslator;
use hammer_core::proto::{
use takana_agent::orchestrator::{ApplyTarget, CompileMode, Orchestrator};
use takana_agent::MockTranslator;
use takana_core::proto::{
Cap, Command, Event, EvidenceCheckVerdict, EvidenceVerdict, Peer, PROTOCOL_VERSION,
};
use hammer_core::swm::{Base, Mutation, Swm, SwmBuild};
use hammer_core::{BaseRef, Evidence, EvidenceCheck, EvidenceKind};
use takana_core::swm::{Base, Mutation, Swm, SwmBuild};
use takana_core::{BaseRef, Evidence, EvidenceCheck, EvidenceKind};
use std::collections::BTreeMap;
/// Bus stub que responde a `Compile` con `BuildReady` cuyo veredicto tiene `all_passed=false`.
@@ -11,12 +11,12 @@ use std::sync::{Arc, Mutex};
use std::thread;
use std::time::Duration;
use hammer_core::proto::{Cap, Command, Event, Peer, PROTOCOL_VERSION};
use hammer_core::swm::{Base, Mutation};
use hammer_core::{BaseRef, Swm};
use takana_core::proto::{Cap, Command, Event, Peer, PROTOCOL_VERSION};
use takana_core::swm::{Base, Mutation};
use takana_core::{BaseRef, Swm};
use hammer_agent::orchestrator::{ApplyTarget, CompileMode, RepairPolicy};
use hammer_agent::{MockTranslator, Orchestrator};
use takana_agent::orchestrator::{ApplyTarget, CompileMode, RepairPolicy};
use takana_agent::{MockTranslator, Orchestrator};
/// El plan por conexión: lista de eventos a empujar tras el Welcome. El stub itera
/// por las conexiones en orden y consume un slot del plan por cada una.
@@ -112,7 +112,7 @@ fn swm_touch(path: &str) -> Swm {
mutations: vec![Mutation::FileDrop {
path: path.into(),
// hash de "X" (un byte 0x58) — calculado con BLAKE3.
content_hash: hammer_core::ArtifactHash::of_inputs(&[b"X"]).as_str().to_string(),
content_hash: takana_core::ArtifactHash::of_inputs(&[b"X"]).as_str().to_string(),
content_b64: Some("WA==".into()),
content_url: None,
}],
@@ -1,5 +1,5 @@
[package]
name = "hammer-bootstrap"
name = "takana-bootstrap"
version.workspace = true
edition.workspace = true
license.workspace = true
@@ -8,8 +8,8 @@ repository.workspace = true
description = "Bootstrap from-scratch de hammer: Stage 0/1/2 hacia el auto-alojamiento (SDD 11)."
[dependencies]
hammer-core.workspace = true
hammer-build.workspace = true
takana-core.workspace = true
takana-build.workspace = true
anyhow.workspace = true
thiserror.workspace = true
tracing.workspace = true
@@ -17,8 +17,8 @@ pub mod manifest;
use std::path::{Path, PathBuf};
use std::process::Command;
use hammer_build::download;
use hammer_core::{ArtifactHash, Recipe, Store};
use takana_build::download;
use takana_core::{ArtifactHash, Recipe, Store};
pub use manifest::{BootstrapManifest, StageEntry};
@@ -27,7 +27,7 @@ pub enum Error {
#[error("io: {0}")]
Io(#[from] std::io::Error),
#[error("core: {0}")]
Core(#[from] hammer_core::Error),
Core(#[from] takana_core::Error),
#[error("tar -x de la semilla falló (exit {0:?})")]
Untar(Option<i32>),
#[error("bootstrap: {0}")]
@@ -147,17 +147,17 @@ fn locate_tool_dir(base: &Path, tool: &str) -> Result<PathBuf> {
}
}
/// Deriva una [`hammer_build::BuildConfig`] desde `base` cuyo `zig_dir` apunta a la **semilla
/// Deriva una [`takana_build::BuildConfig`] desde `base` cuyo `zig_dir` apunta a la **semilla
/// sellada** en vez del toolchain del host: así las recetas del Stage 1 cross-compilan con el
/// toolchain fijado por hash (`seed_hash`), no con el `.dev-fs/tools/zig` de desarrollo. El resto
/// de la config (rootfs, work, cache) se hereda intacto.
pub fn seed_build_config(
base: &hammer_build::BuildConfig,
base: &takana_build::BuildConfig,
seed: &SeedSpec,
store: &Store,
) -> Result<hammer_build::BuildConfig> {
) -> Result<takana_build::BuildConfig> {
let zig_dir = seed.toolchain_dir(store)?;
Ok(hammer_build::BuildConfig { zig_dir, ..base.clone() })
Ok(takana_build::BuildConfig { zig_dir, ..base.clone() })
}
/// Hash de un rootfs sellado por Stage 1. Es un `ArtifactHash` derivado del **contenido lógico**
@@ -265,15 +265,15 @@ pub struct Stage1Spec {
///
/// Idempotente: si el rootfs (mismo hash de contenido) ya está sellado, no reconstruye. El
/// cross-compile real requiere el rootfs base + toolchain del lab; en una máquina sin `.dev-fs`
/// `hammer_build::build` falla limpio antes de ensamblar nada.
/// `takana_build::build` falla limpio antes de ensamblar nada.
pub fn stage1(
spec: &Stage1Spec,
base_cfg: &hammer_build::BuildConfig,
base_cfg: &takana_build::BuildConfig,
store: &Store,
) -> Result<RootfsHash> {
// Toolchain = la semilla sellada, no el zig del host.
let zig_dir = seed_toolchain_dir(store, &spec.seed_hash, spec.seed_kind)?;
let cfg = hammer_build::BuildConfig { zig_dir, ..base_cfg.clone() };
let cfg = takana_build::BuildConfig { zig_dir, ..base_cfg.clone() };
// 1) Construir cada componente (cacheado por el lab si ya existe).
let mut components: Vec<(String, ArtifactHash)> = Vec::with_capacity(STAGE1_COMPONENTS.len());
@@ -282,7 +282,7 @@ pub fn stage1(
let recipe = Recipe::load_from_path(&recipe_path).map_err(|e| {
Error::Other(format!("receta '{name}' ({}): {e}", recipe_path.display()))
})?;
let h = hammer_build::build(&recipe, &cfg, store)?;
let h = takana_build::build(&recipe, &cfg, store)?;
components.push((name.to_string(), h));
}
@@ -367,7 +367,7 @@ fn assemble_rootfs(
// Cada componente se proyecta al rootfs por hardlink (mismo store, mismo filesystem).
for (name, h) in components {
let dir = store.path_of(h, name);
hammer_build::run_hydrate(&dir, staging, hammer_core::LinkMode::Static, None)?;
takana_build::run_hydrate(&dir, staging, takana_core::LinkMode::Static, None)?;
}
// El sidecar de provenance de cada receta (`.hammer/recipe.toml`) no pertenece al rootfs
@@ -605,14 +605,14 @@ fn assemble_product_rootfs(
base_dir.display()
)));
}
hammer_build::run_hydrate(&base_dir, staging, hammer_core::LinkMode::Static, None)?;
takana_build::run_hydrate(&base_dir, staging, takana_core::LinkMode::Static, None)?;
// 2) userland Rust + servicios ENCIMA (hardlink). El userland va DESPUÉS de la base: sus symlinks
// en /usr/bin reemplazan a los de busybox (run_hydrate sustituye el symlink existente) ⇒ el
// producto corre las tools Rust. Orden userland→servicios (sin colisiones entre sí).
for (name, h) in userland.iter().chain(services.iter()) {
let dir = store.path_of(h, name);
hammer_build::run_hydrate(&dir, staging, hammer_core::LinkMode::Static, None)?;
takana_build::run_hydrate(&dir, staging, takana_core::LinkMode::Static, None)?;
}
let _ = std::fs::remove_dir_all(staging.join(".hammer"));
@@ -681,7 +681,7 @@ fn assemble_product_rootfs(
fn build_components(
names: &[&str],
recipes_dir: &Path,
cfg: &hammer_build::BuildConfig,
cfg: &takana_build::BuildConfig,
store: &Store,
) -> Result<Vec<(String, ArtifactHash)>> {
let mut out = Vec::with_capacity(names.len());
@@ -690,7 +690,7 @@ fn build_components(
let recipe = Recipe::load_from_path(&recipe_path).map_err(|e| {
Error::Other(format!("receta '{name}' ({}): {e}", recipe_path.display()))
})?;
let h = hammer_build::build(&recipe, cfg, store)?;
let h = takana_build::build(&recipe, cfg, store)?;
out.push((name.to_string(), h));
}
Ok(out)
@@ -703,7 +703,7 @@ fn build_components(
/// semilla): cada componente usa su `zig_version` propia, y si ya está sellado sale cacheado.
pub fn product(
base: &RootfsHash,
base_cfg: &hammer_build::BuildConfig,
base_cfg: &takana_build::BuildConfig,
recipes_dir: &Path,
store: &Store,
) -> Result<RootfsHash> {
@@ -725,7 +725,7 @@ pub fn product_from_repo(
base: &RootfsHash,
repo_dir: &Path,
trust_dir: &Path,
base_cfg: &hammer_build::BuildConfig,
base_cfg: &takana_build::BuildConfig,
store: &Store,
) -> Result<RootfsHash> {
// 1) userland + servicios REPRODUCIDOS desde el repo firmado (mismo shape que build_components).
@@ -780,10 +780,10 @@ pub fn install_components_from_repo(
names: &[&str],
repo_dir: &Path,
trust_dir: &Path,
cfg: &hammer_build::BuildConfig,
cfg: &takana_build::BuildConfig,
store: &Store,
) -> Result<Vec<(String, ArtifactHash)>> {
use hammer_core::{RepoIndex, SigStatus, TrustStore};
use takana_core::{RepoIndex, SigStatus, TrustStore};
let index = RepoIndex::load(repo_dir)?;
// Firma del release: exigimos TRUSTED. Un release no firmado / con clave desconocida / con firma
@@ -801,7 +801,7 @@ pub fn install_components_from_repo(
}
}
let catalog_dir = hammer_build::swm_bridge::catalog_dir_for(cfg, None);
let catalog_dir = takana_build::swm_bridge::catalog_dir_for(cfg, None);
std::fs::create_dir_all(&catalog_dir)?;
let mut out = Vec::with_capacity(names.len());
@@ -817,7 +817,7 @@ pub fn install_components_from_repo(
let mutation = source_patch_of(&dep_swm).ok_or_else(|| {
Error::Other(format!("la dep '{}' no es un paquete source_patch", dep.name))
})?;
let dep_recipe = hammer_build::swm_bridge::recipe_from_source_patch(
let dep_recipe = takana_build::swm_bridge::recipe_from_source_patch(
mutation,
Some(&dep.name),
&catalog_dir,
@@ -829,7 +829,7 @@ pub fn install_components_from_repo(
let mutation = source_patch_of(&swm).ok_or_else(|| {
Error::Other(format!("'{name}' no es un paquete source_patch"))
})?;
let hash = hammer_build::swm_bridge::build_source_patch(mutation, cfg, store, None, Some(name))?;
let hash = takana_build::swm_bridge::build_source_patch(mutation, cfg, store, None, Some(name))?;
tracing::info!(%name, %hash, "product: componente reproducido desde el repo firmado");
out.push((name.to_string(), hash));
}
@@ -837,18 +837,18 @@ pub fn install_components_from_repo(
}
/// Lee un `.swm` (YAML) de disco a `Swm`. Equivalente al helper homónimo de la CLI (privado allí).
fn load_swm_file(path: &Path) -> Result<hammer_core::Swm> {
fn load_swm_file(path: &Path) -> Result<takana_core::Swm> {
let yaml = std::fs::read_to_string(path)
.map_err(|e| Error::Other(format!("no pude leer {}: {e}", path.display())))?;
hammer_core::Swm::from_yaml(&yaml)
takana_core::Swm::from_yaml(&yaml)
.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).
fn source_patch_of(swm: &hammer_core::Swm) -> Option<&hammer_core::Mutation> {
fn source_patch_of(swm: &takana_core::Swm) -> Option<&takana_core::Mutation> {
swm.mutations
.iter()
.find(|m| matches!(m, hammer_core::Mutation::SourcePatch { .. }))
.find(|m| matches!(m, takana_core::Mutation::SourcePatch { .. }))
}
// ── Producto ATESTADO: la capa FIRMADA que el gate de arje verifica al boot (I4 / Etapa D) ───────
@@ -966,7 +966,7 @@ fn assemble_attested_product_rootfs(
base_dir.display()
)));
}
hammer_build::run_hydrate(&base_dir, staging, hammer_core::LinkMode::Static, None)?;
takana_build::run_hydrate(&base_dir, staging, takana_core::LinkMode::Static, None)?;
let _ = std::fs::remove_dir_all(staging.join(".hammer"));
// 2) PISAR /usr/bin/arje-zero con el init CON gate. Es un hardlink read-only al store ⇒ romper el
@@ -1071,7 +1071,7 @@ fn assemble_attested_product_rootfs(
/// base quedan intactos (Separación Mecanismo/Política): el atestado es un sello APARTE.
pub fn product_attested(
base: &RootfsHash,
base_cfg: &hammer_build::BuildConfig,
base_cfg: &takana_build::BuildConfig,
recipes_dir: &Path,
store: &Store,
cfg: &AttestConfig,
@@ -1245,7 +1245,7 @@ pub struct AllReport {
pub fn all(
seed: &SeedSpec,
recipes_dir: &Path,
base_cfg: &hammer_build::BuildConfig,
base_cfg: &takana_build::BuildConfig,
store: &Store,
) -> Result<AllReport> {
let seed_hash = stage0(seed, store)?;
@@ -1505,7 +1505,7 @@ fn assemble_builder(spec: &BuilderSpec, store: &Store, staging: &Path) -> Result
stage1_dir.display()
)));
}
hammer_build::run_hydrate(&stage1_dir, staging, hammer_core::LinkMode::Static, None)?;
takana_build::run_hydrate(&stage1_dir, staging, takana_core::LinkMode::Static, None)?;
// 2) Esqueleto del builder: el sandbox de build (/toolchain), el store, el work y la config.
for d in ["toolchain", "store", "work", "usr/bin", "etc/hammer/recipes"] {
@@ -1953,7 +1953,7 @@ mod tests {
fn seed_build_config_points_zig_dir_at_seed() {
let tmp = tempfile::tempdir().unwrap();
let (store, seed) = seal_seed_with_zig_at(tmp.path(), "zig-linux-x86_64-0.13.0/zig");
let base = hammer_build::BuildConfig::defaults_for_store(store.root());
let base = takana_build::BuildConfig::defaults_for_store(store.root());
let cfg = seed_build_config(&base, &seed, &store).unwrap();
assert_eq!(cfg.zig_dir, seed.toolchain_dir(&store).unwrap());
// El resto de la config se hereda sin cambios.
@@ -2360,7 +2360,7 @@ open(os.path.join(os.path.dirname(out),"..","..","bins-seen.txt"),"w").write("\n
let path = recipes_dir.join(format!("{name}.toml"));
let r = Recipe::load_from_path(&path).unwrap_or_else(|e| panic!("parse {name}: {e}"));
assert_eq!(&r.name, name);
let h = hammer_build::artifact_hash(&r, &store, &hammer_core::lab::LabFingerprint::for_tests()).expect("hash");
let h = takana_build::artifact_hash(&r, &store, &takana_core::lab::LabFingerprint::for_tests()).expect("hash");
assert!(h.as_str().starts_with("b3:"));
}
}
@@ -2375,7 +2375,7 @@ open(os.path.join(os.path.dirname(out),"..","..","bins-seen.txt"),"w").write("\n
let path = recipes_dir.join(format!("{name}.toml"));
let r = Recipe::load_from_path(&path).unwrap_or_else(|e| panic!("parse {name}: {e}"));
assert_eq!(&r.name, name);
let h = hammer_build::artifact_hash(&r, &store, &hammer_core::lab::LabFingerprint::for_tests()).expect("hash");
let h = takana_build::artifact_hash(&r, &store, &takana_core::lab::LabFingerprint::for_tests()).expect("hash");
assert!(h.as_str().starts_with("b3:"), "hash con prefijo b3:");
}
}
@@ -2696,7 +2696,7 @@ open(os.path.join(os.path.dirname(out),"..","..","bins-seen.txt"),"w").write("\n
}
let r = Recipe::load_from_path(&path)
.unwrap_or_else(|e| panic!("parse {}: {e}", path.display()));
let h = hammer_build::artifact_hash(&r, &store, &hammer_core::lab::LabFingerprint::for_tests())
let h = takana_build::artifact_hash(&r, &store, &takana_core::lab::LabFingerprint::for_tests())
.unwrap_or_else(|e| panic!("hash {}: {e}", path.display()));
assert!(h.as_str().starts_with("b3:"), "{}: hash con prefijo b3:", path.display());
count += 1;
@@ -10,7 +10,7 @@
use std::path::PathBuf;
use hammer_core::{ArtifactHash, Store};
use takana_core::{ArtifactHash, Store};
use serde::{Deserialize, Serialize};
use crate::{Error, Result};
@@ -1,5 +1,5 @@
[package]
name = "hammer-build"
name = "takana-build"
version.workspace = true
edition.workspace = true
license.workspace = true
@@ -8,7 +8,7 @@ repository.workspace = true
description = "El laboratorio de hammer: sandbox de build (bubblewrap + zig cc) e hidratación."
[dependencies]
hammer-core.workspace = true
takana-core.workspace = true
anyhow.workspace = true
thiserror.workspace = true
tracing.workspace = true
@@ -12,7 +12,7 @@ use std::process::Command;
/// Descarga `url` y devuelve su contenido en memoria. Falla con el stderr de curl si el
/// código de salida no es 0 (incluye errores HTTP gracias a `-f`).
pub fn fetch_url_bytes(url: &str) -> hammer_core::Result<Vec<u8>> {
pub fn fetch_url_bytes(url: &str) -> takana_core::Result<Vec<u8>> {
let out = Command::new("curl")
.args([
"-fsSL",
@@ -25,9 +25,9 @@ pub fn fetch_url_bytes(url: &str) -> hammer_core::Result<Vec<u8>> {
url,
])
.output()
.map_err(|e| hammer_core::Error::Other(anyhow::anyhow!("spawn curl: {e}")))?;
.map_err(|e| takana_core::Error::Other(anyhow::anyhow!("spawn curl: {e}")))?;
if !out.status.success() {
return Err(hammer_core::Error::Other(anyhow::anyhow!(
return Err(takana_core::Error::Other(anyhow::anyhow!(
"curl {url} falló (exit {}): {}",
out.status.code().map(|c| c.to_string()).unwrap_or_else(|| "?".into()),
String::from_utf8_lossy(&out.stderr).trim()
@@ -37,7 +37,7 @@ pub fn fetch_url_bytes(url: &str) -> hammer_core::Result<Vec<u8>> {
}
/// Descarga `url` y lo escribe en `dst` (creando los directorios padre).
pub fn fetch_url_to_file(url: &str, dst: &Path) -> hammer_core::Result<()> {
pub fn fetch_url_to_file(url: &str, dst: &Path) -> takana_core::Result<()> {
let bytes = fetch_url_bytes(url)?;
if let Some(parent) = dst.parent() {
std::fs::create_dir_all(parent)?;
@@ -49,14 +49,14 @@ pub fn fetch_url_to_file(url: &str, dst: &Path) -> hammer_core::Result<()> {
/// Verifica que el sha256 de `path` coincide con `expected` (hex, case-insensitive). Lo usan
/// los consumidores que ingieren una fuente fijada (p. ej. el toolchain semilla del bootstrap,
/// [SDD 11](../../docs/11-bootstrap.md)) y necesitan comprobar integridad **antes** de sellar.
pub fn verify_sha256(path: &Path, expected: &str) -> hammer_core::Result<()> {
pub fn verify_sha256(path: &Path, expected: &str) -> takana_core::Result<()> {
use sha2::{Digest, Sha256};
let mut f = std::fs::File::open(path)?;
let mut hasher = Sha256::new();
std::io::copy(&mut f, &mut hasher)?;
let got = hex::encode(hasher.finalize());
if !got.eq_ignore_ascii_case(expected) {
return Err(hammer_core::Error::Other(anyhow::anyhow!(
return Err(takana_core::Error::Other(anyhow::anyhow!(
"sha256 mismatch en {}: esperado {expected}, obtenido {got}",
path.display()
)));
@@ -1,6 +1,6 @@
//! Fase `fetch`: materializa el árbol fuente de la receta en un directorio trabajable.
//!
//! Dos modos, decididos por la receta (ver `hammer_core::SourceKind`):
//! Dos modos, decididos por la receta (ver `takana_core::SourceKind`):
//!
//! * **Git** — mirror local en `<work_root>/repos/<name>.git` + `git archive` del commit
//! fijado al árbol trabajable. El mirror se reusa entre builds; el árbol es descartable.
@@ -14,10 +14,10 @@
use std::path::{Path, PathBuf};
use std::process::{Command, Stdio};
use hammer_core::{Recipe, SourceKind};
use takana_core::{Recipe, SourceKind};
/// Materializa el árbol fuente en un directorio trabajable y devuelve su ruta.
pub fn fetch(recipe: &Recipe, work_root: &Path) -> hammer_core::Result<PathBuf> {
pub fn fetch(recipe: &Recipe, work_root: &Path) -> takana_core::Result<PathBuf> {
match recipe.source.kind()? {
SourceKind::Git { repo, commit } => fetch_git(recipe, repo, commit, work_root),
SourceKind::Tarball { url, sha256 } => fetch_tarball(recipe, url, sha256, work_root),
@@ -34,16 +34,16 @@ pub fn fetch(recipe: &Recipe, work_root: &Path) -> hammer_core::Result<PathBuf>
///
/// El nombre del workspace lleva el hash del CONTENIDO y no el de la ruta: editar el overlay cambia
/// de directorio, así que un árbol viejo nunca se reusa por accidente.
fn fetch_dir(recipe: &Recipe, dir: &str, work_root: &Path) -> hammer_core::Result<PathBuf> {
fn fetch_dir(recipe: &Recipe, dir: &str, work_root: &Path) -> takana_core::Result<PathBuf> {
let origen = recipe.base_dir.join(dir);
if !origen.is_dir() {
return Err(hammer_core::Error::Other(anyhow::anyhow!(
return Err(takana_core::Error::Other(anyhow::anyhow!(
"source.dir apunta a {} y no es un directorio",
origen.display()
)));
}
let h = hammer_core::ArtifactHash::of_tree(&origen)
.map_err(|e| hammer_core::Error::Other(anyhow::anyhow!("hash del árbol {}: {e}", origen.display())))?;
let h = takana_core::ArtifactHash::of_tree(&origen)
.map_err(|e| takana_core::Error::Other(anyhow::anyhow!("hash del árbol {}: {e}", origen.display())))?;
let bare = h.as_str().strip_prefix("b3:").unwrap_or(h.as_str());
let sources_dir = work_root.join("sources");
std::fs::create_dir_all(&sources_dir)?;
@@ -57,9 +57,9 @@ fn fetch_dir(recipe: &Recipe, dir: &str, work_root: &Path) -> hammer_core::Resul
.arg(format!("{}/.", origen.display()))
.arg(&work_tree)
.status()
.map_err(|e| hammer_core::Error::Other(anyhow::anyhow!("spawn cp -a del source.dir: {e}")))?;
.map_err(|e| takana_core::Error::Other(anyhow::anyhow!("spawn cp -a del source.dir: {e}")))?;
if !st.success() {
return Err(hammer_core::Error::Other(anyhow::anyhow!(
return Err(takana_core::Error::Other(anyhow::anyhow!(
"cp -a de {} falló",
origen.display()
)));
@@ -72,7 +72,7 @@ fn fetch_git(
repo: &str,
commit: &str,
work_root: &Path,
) -> hammer_core::Result<PathBuf> {
) -> takana_core::Result<PathBuf> {
let repos_dir = work_root.join("repos");
let sources_dir = work_root.join("sources");
std::fs::create_dir_all(&repos_dir)?;
@@ -105,7 +105,7 @@ fn fetch_git(
if run_git(&["cat-file", "-e", commit], Some(&mirror)).is_err() {
run_git(&["fetch", "--all", "--tags"], Some(&mirror))?;
run_git(&["cat-file", "-e", commit], Some(&mirror)).map_err(|_| {
hammer_core::Error::Other(anyhow::anyhow!(
takana_core::Error::Other(anyhow::anyhow!(
"commit {commit} no existe en {repo} tras fetch"
))
})?;
@@ -131,7 +131,7 @@ fn fetch_git(
}
/// `git archive <commit> | tar -x -C <destino>`: materializa UN árbol sin pagar un worktree.
fn archivar_en(mirror: &Path, commit: &str, destino: &Path) -> hammer_core::Result<()> {
fn archivar_en(mirror: &Path, commit: &str, destino: &Path) -> takana_core::Result<()> {
let archive = Command::new("git")
.arg("-C")
.arg(mirror)
@@ -139,7 +139,7 @@ fn archivar_en(mirror: &Path, commit: &str, destino: &Path) -> hammer_core::Resu
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.map_err(|e| hammer_core::Error::Other(anyhow::anyhow!("spawn git archive: {e}")))?;
.map_err(|e| takana_core::Error::Other(anyhow::anyhow!("spawn git archive: {e}")))?;
let tar_status = Command::new("tar")
.args(["-x", "-C"])
@@ -148,10 +148,10 @@ fn archivar_en(mirror: &Path, commit: &str, destino: &Path) -> hammer_core::Resu
.stdout(Stdio::inherit())
.stderr(Stdio::inherit())
.status()
.map_err(|e| hammer_core::Error::Other(anyhow::anyhow!("spawn tar -x: {e}")))?;
.map_err(|e| takana_core::Error::Other(anyhow::anyhow!("spawn tar -x: {e}")))?;
if !tar_status.success() {
return Err(hammer_core::Error::Other(anyhow::anyhow!(
return Err(takana_core::Error::Other(anyhow::anyhow!(
"git archive | tar -x falló (tar exit {:?})",
tar_status.code()
)));
@@ -184,13 +184,13 @@ fn materializar_submodulos(
repos_dir: &Path,
url_padre: &str,
profundidad: usize,
) -> hammer_core::Result<()> {
) -> takana_core::Result<()> {
let gitlinks = gitlinks_de(mirror, commit)?;
if gitlinks.is_empty() {
return Ok(());
}
if profundidad >= PROFUNDIDAD_SUBMODULOS {
return Err(hammer_core::Error::Other(anyhow::anyhow!(
return Err(takana_core::Error::Other(anyhow::anyhow!(
"submódulos anidados más de {PROFUNDIDAD_SUBMODULOS} niveles en {url_padre} \
({commit}): parece un ciclo"
)));
@@ -200,7 +200,7 @@ fn materializar_submodulos(
for (sha, ruta) in gitlinks {
let declarada = urls.get(&ruta).ok_or_else(|| {
hammer_core::Error::Other(anyhow::anyhow!(
takana_core::Error::Other(anyhow::anyhow!(
"el commit {commit} declara el submódulo `{ruta}` pero .gitmodules no le da url"
))
})?;
@@ -222,7 +222,7 @@ fn materializar_submodulos(
if run_git(&["cat-file", "-e", &sha], Some(&sub_mirror)).is_err() {
run_git(&["fetch", "--all", "--tags"], Some(&sub_mirror))?;
run_git(&["cat-file", "-e", &sha], Some(&sub_mirror)).map_err(|_| {
hammer_core::Error::Other(anyhow::anyhow!(
takana_core::Error::Other(anyhow::anyhow!(
"el submódulo `{ruta}` pide {sha}, que no existe en {url} tras fetch"
))
})?;
@@ -246,7 +246,7 @@ fn materializar_submodulos(
/// Las entradas de tipo `commit` (modo 160000) del commit: `(sha, ruta)`.
///
/// `-z` porque una ruta puede llevar espacios y el formato sin `-z` los escapa con comillas.
fn gitlinks_de(mirror: &Path, commit: &str) -> hammer_core::Result<Vec<(String, String)>> {
fn gitlinks_de(mirror: &Path, commit: &str) -> takana_core::Result<Vec<(String, String)>> {
let salida = salida_git(&["ls-tree", "-r", "-z", commit], mirror)?;
let mut out = Vec::new();
for reg in salida.split('\0').filter(|r| !r.is_empty()) {
@@ -269,7 +269,7 @@ fn gitlinks_de(mirror: &Path, commit: &str) -> hammer_core::Result<Vec<(String,
fn urls_de_gitmodules(
mirror: &Path,
commit: &str,
) -> hammer_core::Result<std::collections::BTreeMap<String, String>> {
) -> takana_core::Result<std::collections::BTreeMap<String, String>> {
let blob = format!("{commit}:.gitmodules");
let salida = match salida_git(
&[
@@ -365,7 +365,7 @@ fn sanear_para_ruta(url: &str) -> String {
}
/// `git <args>` capturando stdout. Error si el comando falla.
fn salida_git(args: &[&str], cwd: &Path) -> hammer_core::Result<String> {
fn salida_git(args: &[&str], cwd: &Path) -> takana_core::Result<String> {
let out = Command::new("git")
.arg("-C")
.arg(cwd)
@@ -373,9 +373,9 @@ fn salida_git(args: &[&str], cwd: &Path) -> hammer_core::Result<String> {
.stdin(Stdio::null())
.stderr(Stdio::piped())
.output()
.map_err(|e| hammer_core::Error::Other(anyhow::anyhow!("spawn git {args:?}: {e}")))?;
.map_err(|e| takana_core::Error::Other(anyhow::anyhow!("spawn git {args:?}: {e}")))?;
if !out.status.success() {
return Err(hammer_core::Error::Other(anyhow::anyhow!(
return Err(takana_core::Error::Other(anyhow::anyhow!(
"git {args:?} falló (exit {:?})",
out.status.code()
)));
@@ -544,7 +544,7 @@ fn fetch_tarball(
url: &str,
sha256: &str,
work_root: &Path,
) -> hammer_core::Result<PathBuf> {
) -> takana_core::Result<PathBuf> {
let tarballs_dir = work_root.join("tarballs");
let sources_dir = work_root.join("sources");
std::fs::create_dir_all(&tarballs_dir)?;
@@ -595,9 +595,9 @@ fn fetch_tarball(
.stdout(Stdio::inherit())
.stderr(Stdio::inherit())
.status()
.map_err(|e| hammer_core::Error::Other(anyhow::anyhow!("spawn tar -x tarball: {e}")))?;
.map_err(|e| takana_core::Error::Other(anyhow::anyhow!("spawn tar -x tarball: {e}")))?;
if !status.success() {
return Err(hammer_core::Error::Other(anyhow::anyhow!(
return Err(takana_core::Error::Other(anyhow::anyhow!(
"tar -x falló (exit {:?})",
status.code()
)));
@@ -628,7 +628,7 @@ fn fetch_tarball(
/// `{base}/{sha256}.tar` en cada uno, en el orden que dicta [`bases_de_mirror`].
/// `HAMMER_MIRROR_KEY`, si está, se pasa como `--key` (Storage Box por sftp). Sin `HAMMER_MIRROR`
/// el comportamiento es el de siempre: derecho a upstream.
fn fetch_con_mirror(url: &str, sha256: &str, dst: &Path) -> hammer_core::Result<()> {
fn fetch_con_mirror(url: &str, sha256: &str, dst: &Path) -> takana_core::Result<()> {
for base in bases_de_mirror("HAMMER_MIRROR", sha256) {
let espejo = format!("{base}/{sha256}.tar");
// Un fallo acá NO es un error: es el caso normal la primera vez que se ve una fuente, y
@@ -645,7 +645,7 @@ fn fetch_con_mirror(url: &str, sha256: &str, dst: &Path) -> hammer_core::Result<
/// para que la próxima corrida lo vuelva a intentar (no envenenamos la caché).
/// Baja `url` a `dst` con curl. Sin verificar nada: quien llama decide cómo se comprueba —
/// `download_verify` por sha256 (tarballs), `hidratar_desde_bundle` delegando en git (bundles).
fn descargar(url: &str, dst: &Path) -> hammer_core::Result<()> {
fn descargar(url: &str, dst: &Path) -> takana_core::Result<()> {
let mut cmd = Command::new("curl");
cmd.args(["-fL", "--retry", "3", "--connect-timeout", "30"]);
// Clave para el mirror por sftp (Storage Box). Inocua para http/file.
@@ -661,9 +661,9 @@ fn descargar(url: &str, dst: &Path) -> hammer_core::Result<()> {
.stdout(Stdio::inherit())
.stderr(Stdio::inherit())
.status()
.map_err(|e| hammer_core::Error::Other(anyhow::anyhow!("spawn curl: {e}")))?;
.map_err(|e| takana_core::Error::Other(anyhow::anyhow!("spawn curl: {e}")))?;
if !status.success() {
return Err(hammer_core::Error::Other(anyhow::anyhow!(
return Err(takana_core::Error::Other(anyhow::anyhow!(
"curl {url} falló (exit {:?})",
status.code()
)));
@@ -671,7 +671,7 @@ fn descargar(url: &str, dst: &Path) -> hammer_core::Result<()> {
Ok(())
}
fn download_verify(url: &str, sha256: &str, dst: &Path) -> hammer_core::Result<()> {
fn download_verify(url: &str, sha256: &str, dst: &Path) -> takana_core::Result<()> {
let tmp = dst.with_extension("tar.partial");
if let Err(e) = descargar(url, &tmp) {
let _ = std::fs::remove_file(&tmp);
@@ -685,14 +685,14 @@ fn download_verify(url: &str, sha256: &str, dst: &Path) -> hammer_core::Result<(
Ok(())
}
fn verify_sha256(path: &Path, expected: &str) -> hammer_core::Result<()> {
fn verify_sha256(path: &Path, expected: &str) -> takana_core::Result<()> {
use sha2::{Digest, Sha256};
let mut f = std::fs::File::open(path)?;
let mut hasher = Sha256::new();
std::io::copy(&mut f, &mut hasher)?;
let got = hex::encode(hasher.finalize());
if !got.eq_ignore_ascii_case(expected) {
return Err(hammer_core::Error::Other(anyhow::anyhow!(
return Err(takana_core::Error::Other(anyhow::anyhow!(
"sha256 mismatch en {}: esperado {expected}, obtenido {got}",
path.display()
)));
@@ -714,7 +714,7 @@ fn verify_sha256(path: &Path, expected: &str) -> hammer_core::Result<()> {
/// (plan C.2 #5). Ver `docs/runbooks/stage1-vm-boot.md` §8.
///
/// Un crate sin deps de crates.io produce un config vacío y ningún `vendor/`: inocuo.
pub fn vendor_cargo_deps(src: &Path, vendor_dir: &str) -> hammer_core::Result<()> {
pub fn vendor_cargo_deps(src: &Path, vendor_dir: &str) -> takana_core::Result<()> {
let locked = src.join("Cargo.lock").is_file();
let mut args: Vec<&str> = vec!["vendor"];
if locked {
@@ -735,9 +735,9 @@ pub fn vendor_cargo_deps(src: &Path, vendor_dir: &str) -> hammer_core::Result<()
.args(&args)
.stderr(Stdio::inherit())
.output()
.map_err(|e| hammer_core::Error::Other(anyhow::anyhow!("spawn cargo vendor: {e}")))?;
.map_err(|e| takana_core::Error::Other(anyhow::anyhow!("spawn cargo vendor: {e}")))?;
if !output.status.success() {
return Err(hammer_core::Error::Other(anyhow::anyhow!(
return Err(takana_core::Error::Other(anyhow::anyhow!(
"cargo vendor falló (exit {:?})",
output.status.code()
)));
@@ -762,7 +762,7 @@ pub fn vendor_cargo_deps(src: &Path, vendor_dir: &str) -> hammer_core::Result<()
/// `GOFLAGS=-mod=mod` permite poblar el `vendor/` aunque el árbol no lo traiga.
///
/// Un proyecto sin deps externas produce un `vendor/` vacío (o ninguno): inocuo.
pub fn vendor_go_deps(src: &Path) -> hammer_core::Result<()> {
pub fn vendor_go_deps(src: &Path) -> takana_core::Result<()> {
// Proyecto WORKSPACE (multimódulo): si el árbol trae `go.work`, `go mod vendor` con GOWORK=off
// vendorea SÓLO el módulo raíz → el build (que ve todo el workspace) se queja de "inconsistent
// vendoring". `go work vendor` (go 1.22+) produce un `vendor/` consistente para TODO el workspace.
@@ -787,13 +787,13 @@ pub fn vendor_go_deps(src: &Path) -> hammer_core::Result<()> {
.stderr(Stdio::inherit())
.output()
.map_err(|e| {
hammer_core::Error::Other(anyhow::anyhow!(
takana_core::Error::Other(anyhow::anyhow!(
"spawn {label}: {e} (¿está `go` en el PATH del host? lo provee el lab, \
cf. scripts/bootstrap-devfs.sh / vps-setup.sh)"
))
})?;
if !output.status.success() {
return Err(hammer_core::Error::Other(anyhow::anyhow!(
return Err(takana_core::Error::Other(anyhow::anyhow!(
"{label} falló (exit {:?})",
output.status.code()
)));
@@ -802,11 +802,11 @@ pub fn vendor_go_deps(src: &Path) -> hammer_core::Result<()> {
}
/// Aplica los patches de la receta (relativos a `recipe.base_dir`) sobre `tree`.
pub fn apply_patches(recipe: &Recipe, tree: &Path) -> hammer_core::Result<()> {
pub fn apply_patches(recipe: &Recipe, tree: &Path) -> takana_core::Result<()> {
for p in &recipe.source.patches {
let patch_path = recipe.base_dir.join(p);
let bytes = std::fs::read(&patch_path).map_err(|e| {
hammer_core::Error::Recipe(format!(
takana_core::Error::Recipe(format!(
"no pude leer patch {}: {e}",
patch_path.display()
))
@@ -818,18 +818,18 @@ pub fn apply_patches(recipe: &Recipe, tree: &Path) -> hammer_core::Result<()> {
.stdout(Stdio::inherit())
.stderr(Stdio::inherit())
.spawn()
.map_err(|e| hammer_core::Error::Other(anyhow::anyhow!("spawn patch: {e}")))?;
.map_err(|e| takana_core::Error::Other(anyhow::anyhow!("spawn patch: {e}")))?;
if let Some(mut stdin) = child.stdin.take() {
use std::io::Write;
stdin
.write_all(&bytes)
.map_err(|e| hammer_core::Error::Other(anyhow::anyhow!("write patch: {e}")))?;
.map_err(|e| takana_core::Error::Other(anyhow::anyhow!("write patch: {e}")))?;
}
let status = child
.wait()
.map_err(|e| hammer_core::Error::Other(anyhow::anyhow!("wait patch: {e}")))?;
.map_err(|e| takana_core::Error::Other(anyhow::anyhow!("wait patch: {e}")))?;
if !status.success() {
return Err(hammer_core::Error::Other(anyhow::anyhow!(
return Err(takana_core::Error::Other(anyhow::anyhow!(
"patch {} falló (exit {:?})",
patch_path.display(),
status.code()
@@ -1047,7 +1047,7 @@ mod tests {
}
}
fn run_git(args: &[&str], cwd: Option<&Path>) -> hammer_core::Result<()> {
fn run_git(args: &[&str], cwd: Option<&Path>) -> takana_core::Result<()> {
let mut cmd = Command::new("git");
if let Some(c) = cwd {
cmd.arg("-C").arg(c);
@@ -1058,9 +1058,9 @@ fn run_git(args: &[&str], cwd: Option<&Path>) -> hammer_core::Result<()> {
.stdout(Stdio::inherit())
.stderr(Stdio::inherit())
.status()
.map_err(|e| hammer_core::Error::Other(anyhow::anyhow!("spawn git {args:?}: {e}")))?;
.map_err(|e| takana_core::Error::Other(anyhow::anyhow!("spawn git {args:?}: {e}")))?;
if !status.success() {
return Err(hammer_core::Error::Other(anyhow::anyhow!(
return Err(takana_core::Error::Other(anyhow::anyhow!(
"git {args:?} falló (exit {:?})",
status.code()
)));
@@ -70,19 +70,19 @@ impl Harkaq {
deps: &[PathBuf],
contratos: &[String],
dst: &Path,
) -> hammer_core::Result<()> {
) -> takana_core::Result<()> {
let mut ro: Vec<String> = Vec::new();
let mut dirs: std::collections::BTreeSet<String> = Default::default();
for dep in deps {
let real = std::fs::canonicalize(dep).map_err(|e| {
hammer_core::Error::Other(anyhow::anyhow!("canonicalize {}: {e}", dep.display()))
takana_core::Error::Other(anyhow::anyhow!("canonicalize {}: {e}", dep.display()))
})?;
recorrer(&real, &real, &mut ro, &mut dirs)?;
}
let mut f = std::fs::File::create(dst).map_err(|e| {
hammer_core::Error::Other(anyhow::anyhow!("crear política {}: {e}", dst.display()))
takana_core::Error::Other(anyhow::anyhow!("crear política {}: {e}", dst.display()))
})?;
writeln!(f, "# harkaq: clausura derivada de {} dep(s)", deps.len()).ok();
@@ -98,7 +98,7 @@ impl Harkaq {
if let Some(base) = &self.base {
let txt = std::fs::read_to_string(base).map_err(|e| {
hammer_core::Error::Other(anyhow::anyhow!("leer HARKAQ_BASE {}: {e}", base.display()))
takana_core::Error::Other(anyhow::anyhow!("leer HARKAQ_BASE {}: {e}", base.display()))
})?;
writeln!(f, "# runtime base ({})", base.display()).ok();
f.write_all(txt.as_bytes()).ok();
@@ -130,7 +130,7 @@ impl Harkaq {
deps: &[PathBuf],
contratos: &[String],
id: &str,
) -> hammer_core::Result<Corrida> {
) -> takana_core::Result<Corrida> {
let tmp = std::env::temp_dir().join(format!("harkaq-{id}-{}", std::process::id()));
std::fs::create_dir_all(&tmp).ok();
let policy = tmp.join("policy");
@@ -146,7 +146,7 @@ impl Harkaq {
let verdict = tmp.join("verdict.json");
let vf = std::fs::File::create(&verdict).map_err(|e| {
hammer_core::Error::Other(anyhow::anyhow!("crear verdict: {e}"))
takana_core::Error::Other(anyhow::anyhow!("crear verdict: {e}"))
})?;
let reader = Command::new(self.bin.join("harkaq-audit"))
.arg("--canary")
@@ -157,7 +157,7 @@ impl Harkaq {
.stderr(Stdio::inherit())
.spawn()
.map_err(|e| {
hammer_core::Error::Other(anyhow::anyhow!(
takana_core::Error::Other(anyhow::anyhow!(
"spawn harkaq-audit ({}): {e} — ¿compilado y con `setcap cap_audit_read`?",
self.bin.display()
))
@@ -199,7 +199,7 @@ fn recorrer(
dir: &Path,
ro: &mut Vec<String>,
dirs: &mut std::collections::BTreeSet<String>,
) -> hammer_core::Result<()> {
) -> takana_core::Result<()> {
let rd = match std::fs::read_dir(dir) {
Ok(r) => r,
Err(_) => return Ok(()),
@@ -15,7 +15,7 @@ use std::os::unix::fs::FileTypeExt;
use std::path::{Path, PathBuf};
use std::process::Command;
use hammer_core::LinkMode;
use takana_core::LinkMode;
#[derive(Debug, Clone)]
pub struct HydratedFile {
@@ -61,7 +61,7 @@ pub fn hydrate(
target_fhs: &Path,
mode: LinkMode,
dynamic: Option<&DynamicSpec>,
) -> hammer_core::Result<HydrateReport> {
) -> takana_core::Result<HydrateReport> {
// `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
// en `Store::seal` (2026-08-29): un vacío no es un artefacto, es un nombre. Acá duele especial
@@ -71,7 +71,7 @@ pub fn hydrate(
.map(|mut e| e.next().is_none())
.unwrap_or(true);
if !artifact_dir.is_dir() || vacio {
return Err(hammer_core::Error::Store(format!(
return Err(takana_core::Error::Store(format!(
"artefacto inexistente o VACÍO: {}",
artifact_dir.display()
)));
@@ -93,7 +93,7 @@ pub fn hydrate(
// La raíz REAL contra la que se mide la contención (ver `no_escapa`). Se resuelve una sola vez:
// si el propio `target_fhs` es un symlink, eso es elección de quien hidrata y vale como raíz.
let raiz = target_fhs.canonicalize().map_err(|e| {
hammer_core::Error::Store(format!("canonicalize {}: {e}", target_fhs.display()))
takana_core::Error::Store(format!("canonicalize {}: {e}", target_fhs.display()))
})?;
let mut report = HydrateReport::default();
@@ -121,12 +121,12 @@ pub fn hydrate(
/// entre las dos. Para el fallo real —un symlink persistente puesto por otro artefacto— alcanza y
/// es determinista; la versión sin carrera es mover las escrituras a `openat2` + `linkat`/`renameat`
/// (SDD 25 §7-H3), y eso arrastra el camino de `patchelf`.
fn no_escapa(raiz: &Path, dst: &Path) -> hammer_core::Result<()> {
fn no_escapa(raiz: &Path, dst: &Path) -> takana_core::Result<()> {
let real = dst.canonicalize().map_err(|e| {
hammer_core::Error::Store(format!("canonicalize {}: {e}", dst.display()))
takana_core::Error::Store(format!("canonicalize {}: {e}", dst.display()))
})?;
if !real.starts_with(raiz) {
return Err(hammer_core::Error::Store(format!(
return Err(takana_core::Error::Store(format!(
"la hidratación se saldría del root: {} lleva a {}, fuera de {}.\n\
Es un symlink de directorio ya presente en el FHS (probablemente puesto por otro \
artefacto). No se escribe a través de él: borralo o hidratá sobre un root limpio.",
@@ -145,7 +145,7 @@ fn walk_and_link(
raiz: &Path,
spec: Option<&DynamicSpec>,
report: &mut HydrateReport,
) -> hammer_core::Result<()> {
) -> takana_core::Result<()> {
for entry in std::fs::read_dir(cur)? {
let entry = entry?;
let src = entry.path();
@@ -183,7 +183,7 @@ fn walk_and_link(
report.files.push(HydratedFile { src, dst });
} else if ft.is_block_device() || ft.is_char_device() || ft.is_fifo() || ft.is_socket() {
// Los artefactos del lab no deberían contener nodos especiales; lo señalamos.
return Err(hammer_core::Error::Other(anyhow::anyhow!(
return Err(takana_core::Error::Other(anyhow::anyhow!(
"tipo no soportado en {}: {ft:?}",
src.display()
)));
@@ -195,7 +195,7 @@ fn walk_and_link(
/// Crea un hardlink `dst → src` reemplazando atómicamente cualquier archivo previo en `dst`.
/// Implementación: `link(src, dst.tmp)` + `rename(dst.tmp, dst)`. La técnica garantiza que
/// el archivo en `dst` nunca queda en estado intermedio y que el inode del store no se toca.
fn atomic_hardlink(src: &Path, dst: &Path) -> hammer_core::Result<()> {
fn atomic_hardlink(src: &Path, dst: &Path) -> takana_core::Result<()> {
if let Some(parent) = dst.parent() {
std::fs::create_dir_all(parent)?;
}
@@ -203,7 +203,7 @@ fn atomic_hardlink(src: &Path, dst: &Path) -> hammer_core::Result<()> {
// Limpia un tmp olvidado de una corrida anterior.
let _ = std::fs::remove_file(&tmp);
std::fs::hard_link(src, &tmp).map_err(|e| {
hammer_core::Error::Store(format!(
takana_core::Error::Store(format!(
"hardlink {} → {}: {e}",
src.display(),
tmp.display()
@@ -212,7 +212,7 @@ fn atomic_hardlink(src: &Path, dst: &Path) -> hammer_core::Result<()> {
// rename sobreescribe atómicamente si dst es un archivo (no si es directorio).
if let Err(e) = std::fs::rename(&tmp, dst) {
let _ = std::fs::remove_file(&tmp);
return Err(hammer_core::Error::Store(format!(
return Err(takana_core::Error::Store(format!(
"rename {} → {}: {e}",
tmp.display(),
dst.display()
@@ -223,7 +223,7 @@ fn atomic_hardlink(src: &Path, dst: &Path) -> hammer_core::Result<()> {
/// `true` si el archivo empieza por el magic ELF (`\x7fELF`). Errores de I/O se propagan;
/// un archivo más corto que 4 bytes simplemente no es ELF.
fn is_elf(path: &Path) -> hammer_core::Result<bool> {
fn is_elf(path: &Path) -> takana_core::Result<bool> {
use std::io::Read;
let mut f = std::fs::File::open(path)?;
let mut magic = [0u8; 4];
@@ -235,14 +235,14 @@ fn is_elf(path: &Path) -> hammer_core::Result<bool> {
}
/// Verifica que `patchelf` esté disponible antes de empezar a proyectar.
fn ensure_patchelf() -> hammer_core::Result<()> {
fn ensure_patchelf() -> takana_core::Result<()> {
match Command::new("patchelf").arg("--version").output() {
Ok(o) if o.status.success() => Ok(()),
Ok(o) => Err(hammer_core::Error::Other(anyhow::anyhow!(
Ok(o) => Err(takana_core::Error::Other(anyhow::anyhow!(
"patchelf falló al invocarse: {}",
String::from_utf8_lossy(&o.stderr).trim()
))),
Err(e) => Err(hammer_core::Error::Other(anyhow::anyhow!(
Err(e) => Err(takana_core::Error::Other(anyhow::anyhow!(
"hidratación dinámica requiere `patchelf` en el PATH y no se pudo ejecutar: {e}"
))),
}
@@ -251,14 +251,14 @@ fn ensure_patchelf() -> hammer_core::Result<()> {
/// Copia `src → dst` atómicamente (copy a tmp → patchelf el tmp → rename), preservando los
/// permisos (`std::fs::copy` los replica, incluido el bit de ejecución). Patcheamos el tmp
/// **antes** del rename para que `dst` nunca quede en un estado a medio parchear.
fn atomic_copy_and_patch(src: &Path, dst: &Path, spec: &DynamicSpec) -> hammer_core::Result<()> {
fn atomic_copy_and_patch(src: &Path, dst: &Path, spec: &DynamicSpec) -> takana_core::Result<()> {
if let Some(parent) = dst.parent() {
std::fs::create_dir_all(parent)?;
}
let tmp = with_suffix(dst, ".hammer-tmp");
let _ = std::fs::remove_file(&tmp);
std::fs::copy(src, &tmp).map_err(|e| {
hammer_core::Error::Store(format!("copy {}{}: {e}", src.display(), tmp.display()))
takana_core::Error::Store(format!("copy {}{}: {e}", src.display(), tmp.display()))
})?;
if let Err(e) = run_patchelf(&tmp, spec) {
let _ = std::fs::remove_file(&tmp);
@@ -266,7 +266,7 @@ fn atomic_copy_and_patch(src: &Path, dst: &Path, spec: &DynamicSpec) -> hammer_c
}
if let Err(e) = std::fs::rename(&tmp, dst) {
let _ = std::fs::remove_file(&tmp);
return Err(hammer_core::Error::Store(format!(
return Err(takana_core::Error::Store(format!(
"rename {} → {}: {e}",
tmp.display(),
dst.display()
@@ -277,7 +277,7 @@ fn atomic_copy_and_patch(src: &Path, dst: &Path, spec: &DynamicSpec) -> hammer_c
/// Ejecuta `patchelf [--set-interpreter I] [--set-rpath R] <file>`. Asume que el caller ya
/// comprobó (`has_work`) que hay al menos una de las dos.
fn run_patchelf(file: &Path, spec: &DynamicSpec) -> hammer_core::Result<()> {
fn run_patchelf(file: &Path, spec: &DynamicSpec) -> takana_core::Result<()> {
let mut cmd = Command::new("patchelf");
if let Some(interp) = &spec.interpreter {
cmd.arg("--set-interpreter").arg(interp);
@@ -287,13 +287,13 @@ fn run_patchelf(file: &Path, spec: &DynamicSpec) -> hammer_core::Result<()> {
}
cmd.arg(file);
let out = cmd.output().map_err(|e| {
hammer_core::Error::Other(anyhow::anyhow!(
takana_core::Error::Other(anyhow::anyhow!(
"no pude ejecutar patchelf sobre {}: {e}",
file.display()
))
})?;
if !out.status.success() {
return Err(hammer_core::Error::Other(anyhow::anyhow!(
return Err(takana_core::Error::Other(anyhow::anyhow!(
"patchelf falló sobre {}: {}",
file.display(),
String::from_utf8_lossy(&out.stderr).trim()
@@ -302,7 +302,7 @@ fn run_patchelf(file: &Path, spec: &DynamicSpec) -> hammer_core::Result<()> {
Ok(())
}
fn atomic_replace_symlink(link_target: &Path, dst: &Path) -> hammer_core::Result<()> {
fn atomic_replace_symlink(link_target: &Path, dst: &Path) -> takana_core::Result<()> {
use std::os::unix::fs::symlink;
if let Some(parent) = dst.parent() {
std::fs::create_dir_all(parent)?;
@@ -310,11 +310,11 @@ fn atomic_replace_symlink(link_target: &Path, dst: &Path) -> hammer_core::Result
let tmp = with_suffix(dst, ".hammer-tmp");
let _ = std::fs::remove_file(&tmp);
symlink(link_target, &tmp).map_err(|e| {
hammer_core::Error::Store(format!("symlink {}{}: {e}", link_target.display(), tmp.display()))
takana_core::Error::Store(format!("symlink {}{}: {e}", link_target.display(), tmp.display()))
})?;
if let Err(e) = std::fs::rename(&tmp, dst) {
let _ = std::fs::remove_file(&tmp);
return Err(hammer_core::Error::Store(format!(
return Err(takana_core::Error::Store(format!(
"rename {} → {}: {e}",
tmp.display(),
dst.display()
@@ -4,7 +4,7 @@
use std::collections::HashMap;
use std::path::{Path, PathBuf};
use hammer_core::{ArtifactHash, Compiler, LinkMode, Phases, Recipe, Store};
use takana_core::{ArtifactHash, Compiler, LinkMode, Phases, Recipe, Store};
pub mod config;
pub mod download;
@@ -33,8 +33,8 @@ pub use swm_bridge::build_source_patch;
pub fn artifact_hash(
recipe: &Recipe,
_store: &Store,
lab: &hammer_core::lab::LabFingerprint,
) -> hammer_core::Result<ArtifactHash> {
lab: &takana_core::lab::LabFingerprint,
) -> takana_core::Result<ArtifactHash> {
let mut stack: Vec<String> = Vec::new();
// Memo por path canónico de receta: el grafo de build es un DAG-diamante (p.ej. TODO KF6/Qt
// converge en qtbase/kcoreaddons, alcanzables por decenas de rutas). Sin memoizar, el walk
@@ -50,11 +50,11 @@ fn artifact_hash_rec(
recipe: &Recipe,
stack: &mut Vec<String>,
memo: &mut HashMap<PathBuf, ArtifactHash>,
lab: &hammer_core::lab::LabFingerprint,
) -> hammer_core::Result<ArtifactHash> {
lab: &takana_core::lab::LabFingerprint,
) -> takana_core::Result<ArtifactHash> {
if stack.iter().any(|n| n == &recipe.name) {
stack.push(recipe.name.clone());
return Err(hammer_core::Error::Recipe(format!(
return Err(takana_core::Error::Recipe(format!(
"ciclo de dependencias de build: {}",
stack.join(" -> ")
)));
@@ -92,13 +92,13 @@ fn resolve_build_dep_hashes(
recipe: &Recipe,
stack: &mut Vec<String>,
memo: &mut HashMap<PathBuf, ArtifactHash>,
lab: &hammer_core::lab::LabFingerprint,
) -> hammer_core::Result<Vec<ArtifactHash>> {
lab: &takana_core::lab::LabFingerprint,
) -> takana_core::Result<Vec<ArtifactHash>> {
if recipe.deps.build.is_empty() {
return Ok(Vec::new());
}
if recipe.base_dir.as_os_str().is_empty() {
return Err(hammer_core::Error::Recipe(format!(
return Err(takana_core::Error::Recipe(format!(
"la receta '{}' declara deps de build {:?} pero no tiene base_dir; \
cárgala con Recipe::load_from_path para resolver el catálogo",
recipe.name, recipe.deps.build
@@ -116,7 +116,7 @@ fn resolve_build_dep_hashes(
continue;
}
let dep_recipe = Recipe::load_from_path(&dep_path).map_err(|e| {
hammer_core::Error::Recipe(format!(
takana_core::Error::Recipe(format!(
"no pude cargar la dep de build '{dep}' de '{}' ({}): {e}",
recipe.name,
dep_path.display()
@@ -138,12 +138,12 @@ fn materialize_build_deps(
recipe: &Recipe,
cfg: &BuildConfig,
store: &Store,
) -> hammer_core::Result<Vec<PathBuf>> {
) -> takana_core::Result<Vec<PathBuf>> {
if recipe.deps.build.is_empty() {
return Ok(Vec::new());
}
if recipe.base_dir.as_os_str().is_empty() {
return Err(hammer_core::Error::Recipe(format!(
return Err(takana_core::Error::Recipe(format!(
"la receta '{}' declara deps de build {:?} pero no tiene base_dir; \
cárgala con Recipe::load_from_path para resolver el catálogo",
recipe.name, recipe.deps.build
@@ -153,7 +153,7 @@ fn materialize_build_deps(
for dep in &recipe.deps.build {
let dep_path = resolve_dep_path(&recipe.base_dir, dep);
let dep_recipe = Recipe::load_from_path(&dep_path).map_err(|e| {
hammer_core::Error::Recipe(format!(
takana_core::Error::Recipe(format!(
"no pude cargar la dep de build '{dep}' de '{}' ({}): {e}",
recipe.name,
dep_path.display()
@@ -169,14 +169,14 @@ fn materialize_build_deps(
/// la receta fije `build.zig_version`, en cuyo caso se usa el zig de esa versión, ubicado como
/// **hermano** del por defecto (`<…>/zig-x86_64-linux-<version>`, el layout de `.dev-fs/tools/`).
/// Escotilla por-receta para esquivar la regresión de zig 0.14 sin tocar el toolchain global.
fn effective_zig_dir(cfg: &BuildConfig, recipe: &Recipe) -> hammer_core::Result<PathBuf> {
fn effective_zig_dir(cfg: &BuildConfig, recipe: &Recipe) -> takana_core::Result<PathBuf> {
let Some(version) = &recipe.build.zig_version else {
return Ok(cfg.zig_dir.clone());
};
let parent = cfg.zig_dir.parent().unwrap_or_else(|| Path::new("."));
let dir = parent.join(format!("zig-x86_64-linux-{version}"));
if !dir.join("zig").is_file() {
return Err(hammer_core::Error::Recipe(format!(
return Err(takana_core::Error::Recipe(format!(
"zig_version=\"{version}\": no encuentro el ejecutable zig en {} \
(bajá ese zig a {}/)",
dir.display(),
@@ -192,13 +192,13 @@ pub fn build(
recipe: &Recipe,
cfg: &BuildConfig,
store: &Store,
) -> hammer_core::Result<ArtifactHash> {
) -> takana_core::Result<ArtifactHash> {
// La huella sale del rootfs de ESTE cfg, que es el lab que de verdad va a compilar — no de
// `docs/state/lab-toolchain.lock` ni de una ruta derivada del store. Derivarla del padre del
// store ya mordió una vez (ver `BuildConfig::defaults_for_store_with_lab`): al anclar el store
// de un worker a un volumen, el lab se buscaba donde no estaba. `cfg.rootfs` respeta
// `HAMMER_LAB`/`HAMMER_ROOTFS` y es la única fuente que no puede desincronizarse del build.
let lab = hammer_core::lab::LabFingerprint::from_rootfs(&cfg.rootfs)?;
let lab = takana_core::lab::LabFingerprint::from_rootfs(&cfg.rootfs)?;
let h = artifact_hash(recipe, store, &lab)?;
if store.has(&h, &recipe.name) {
tracing::info!(hash = %h, name = %recipe.name, "caché: artefacto ya en el store");
@@ -342,7 +342,7 @@ pub fn build(
tracing::info!(cmd, "phase: compile");
sb.run(cmd)?;
} else {
return Err(hammer_core::Error::Recipe(
return Err(takana_core::Error::Recipe(
"receta sin fase 'compile' y heurística no encontró ninguna".into(),
));
}
@@ -350,7 +350,7 @@ pub fn build(
tracing::info!(cmd, "phase: install");
sb.run(cmd)?;
} else {
return Err(hammer_core::Error::Recipe(
return Err(takana_core::Error::Recipe(
"receta sin fase 'install' y heurística no encontró ninguna".into(),
));
}
@@ -404,7 +404,7 @@ pub fn build(
// seal, para que quede congelada en el árbol read-only del store. `hammer export` la
// 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).
let sidecar_path = out_dir.join(hammer_core::store::RECIPE_SIDECAR_REL);
let sidecar_path = out_dir.join(takana_core::store::RECIPE_SIDECAR_REL);
if let Some(parent) = sidecar_path.parent() {
std::fs::create_dir_all(parent)?;
}
@@ -419,9 +419,9 @@ pub fn build(
/// El resultado de correr un check de evidencia, con su veredicto (H1b).
#[derive(Debug, Clone)]
pub struct EvidenceOutcome {
pub kind: hammer_core::EvidenceKind,
pub kind: takana_core::EvidenceKind,
pub cmd: String,
pub outcome: hammer_core::CheckOutcome,
pub outcome: takana_core::CheckOutcome,
}
/// Reporte de la ejecución de TODA la evidencia de una receta (H1b). `all_passed` es el gate:
@@ -437,7 +437,7 @@ impl EvidenceReport {
}
/// El estrato de confianza MÁS ALTO que pasó (para reportar "verificado hasta <nivel>"). `None`
/// si no hay checks. No promete que ese nivel *baste* — sólo que lo declarado a ese nivel pasó.
pub fn max_level_passed(&self) -> Option<hammer_core::EvidenceKind> {
pub fn max_level_passed(&self) -> Option<takana_core::EvidenceKind> {
self.results
.iter()
.filter(|r| r.outcome.passed)
@@ -460,7 +460,7 @@ pub fn run_evidence(
recipe: &Recipe,
cfg: &BuildConfig,
store: &Store,
) -> hammer_core::Result<EvidenceReport> {
) -> takana_core::Result<EvidenceReport> {
recipe.evidence.validate()?;
if recipe.evidence.checks.is_empty() {
return Ok(EvidenceReport { results: Vec::new() });
@@ -615,7 +615,7 @@ fn inject_cargo_package_selector(recipe: &mut Recipe, src: &Path) {
/// NO la considere parte del workspace del repo hammer (que la contiene en `work/sources/`). Append
/// 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.
fn ensure_cargo_workspace_isolation(src: &Path) -> hammer_core::Result<()> {
fn ensure_cargo_workspace_isolation(src: &Path) -> takana_core::Result<()> {
let manifest = src.join("Cargo.toml");
let text = std::fs::read_to_string(&manifest)?;
// Heurística simple: ¿hay una tabla `[workspace]` (al inicio de línea)? Cubre `[workspace]` y
@@ -804,7 +804,7 @@ fn cargo_linker_env_var(triple: &str) -> String {
/// Devuelve los comandos a ejecutar para cada fase: overrides explícitos en la receta
/// ganan; el resto lo deriva la heurística según el sistema de build detectado.
fn resolve_phases(recipe: &Recipe, src: &Path) -> hammer_core::Result<Phases> {
fn resolve_phases(recipe: &Recipe, src: &Path) -> takana_core::Result<Phases> {
let mut out = recipe.build.phases.clone();
// Raíz efectiva del módulo Go: `<src>/<subdir>` si la receta lo fija (dolt→go, csvtk→subdir),
// si no `src`. detect/detect_go_main operan ahí; el compile hace `cd <subdir>` primero.
@@ -1303,7 +1303,7 @@ commit = "deadbeef"
let d = tempfile::tempdir().unwrap();
touch(d.path(), "Cargo.toml");
let mut r = recipe(&[]);
r.build.link = hammer_core::LinkMode::Dynamic;
r.build.link = takana_core::LinkMode::Dynamic;
let c = resolve_phases(&r, d.path()).unwrap().compile.unwrap();
assert!(c.contains("target-feature=-crt-static"), "{c}");
}
@@ -1359,7 +1359,7 @@ fn unique_out_dir(
work_root: &Path,
name: &str,
h: &ArtifactHash,
) -> hammer_core::Result<PathBuf> {
) -> takana_core::Result<PathBuf> {
let dir = work_root
.join("out")
.join(h.store_dir_name(name));
@@ -1378,7 +1378,7 @@ pub fn hydrate(
target_fhs: &Path,
mode: LinkMode,
dynamic: Option<&hydrate::DynamicSpec>,
) -> hammer_core::Result<HydrateReport> {
) -> takana_core::Result<HydrateReport> {
let artifact_dir = store.find_by_hash(h.as_str())?;
hydrate::hydrate(&artifact_dir, target_fhs, mode, dynamic)
}
@@ -1417,8 +1417,8 @@ mod dep_hash_tests {
write_recipe(d.path(), "app", "a0", &["base"]);
write_recipe(d.path(), "app_nodep", "a0", &[]); // misma fuente, sin la dep
let with_dep = artifact_hash(&load(d.path(), "app"), &store, &hammer_core::lab::LabFingerprint::for_tests()).unwrap();
let no_dep = artifact_hash(&load(d.path(), "app_nodep"), &store, &hammer_core::lab::LabFingerprint::for_tests()).unwrap();
let with_dep = artifact_hash(&load(d.path(), "app"), &store, &takana_core::lab::LabFingerprint::for_tests()).unwrap();
let no_dep = artifact_hash(&load(d.path(), "app_nodep"), &store, &takana_core::lab::LabFingerprint::for_tests()).unwrap();
// `app_nodep` se llama distinto, pero el nombre no entra al hash: la única diferencia
// de entrada es la presencia de la dep. (Renombramos sólo para tener dos .toml.)
assert_ne!(with_dep, no_dep, "la dep de build debe entrar al hash");
@@ -1431,11 +1431,11 @@ mod dep_hash_tests {
write_recipe(d.path(), "base", "b0", &[]);
write_recipe(d.path(), "lib", "l0", &["base"]);
write_recipe(d.path(), "app", "a0", &["lib"]);
let before = artifact_hash(&load(d.path(), "app"), &store, &hammer_core::lab::LabFingerprint::for_tests()).unwrap();
let before = artifact_hash(&load(d.path(), "app"), &store, &takana_core::lab::LabFingerprint::for_tests()).unwrap();
// Cambiar la fuente de la dep transitiva `base` debe re-hashear `app`.
write_recipe(d.path(), "base", "b1", &[]);
let after = artifact_hash(&load(d.path(), "app"), &store, &hammer_core::lab::LabFingerprint::for_tests()).unwrap();
let after = artifact_hash(&load(d.path(), "app"), &store, &takana_core::lab::LabFingerprint::for_tests()).unwrap();
assert_ne!(before, after, "un cambio transitivo debe propagarse");
}
@@ -1444,7 +1444,7 @@ mod dep_hash_tests {
let d = tempfile::tempdir().unwrap();
let store = store_in(d.path());
write_recipe(d.path(), "app", "a0", &["pcre2"]); // sin pcre2.toml
let err = artifact_hash(&load(d.path(), "app"), &store, &hammer_core::lab::LabFingerprint::for_tests()).unwrap_err().to_string();
let err = artifact_hash(&load(d.path(), "app"), &store, &takana_core::lab::LabFingerprint::for_tests()).unwrap_err().to_string();
assert!(err.contains("pcre2"), "el error debe nombrar la dep: {err}");
assert!(err.contains("app"), "y la receta que la pide: {err}");
}
@@ -1455,7 +1455,7 @@ mod dep_hash_tests {
let store = store_in(d.path());
write_recipe(d.path(), "a", "a0", &["b"]);
write_recipe(d.path(), "b", "b0", &["a"]);
let err = artifact_hash(&load(d.path(), "a"), &store, &hammer_core::lab::LabFingerprint::for_tests()).unwrap_err().to_string();
let err = artifact_hash(&load(d.path(), "a"), &store, &takana_core::lab::LabFingerprint::for_tests()).unwrap_err().to_string();
assert!(err.contains("ciclo"), "esperaba ciclo: {err}");
assert!(err.contains("a -> b -> a"), "debe mostrar la cadena: {err}");
}
@@ -1468,7 +1468,7 @@ mod dep_hash_tests {
"name=\"x\"\nversion=\"1\"\n[source]\nrepo=\"git://x\"\ncommit=\"c0\"\n[build]\n[deps]\nbuild=[\"y\"]\n",
)
.unwrap();
let err = artifact_hash(&r, &store, &hammer_core::lab::LabFingerprint::for_tests()).unwrap_err().to_string();
let err = artifact_hash(&r, &store, &takana_core::lab::LabFingerprint::for_tests()).unwrap_err().to_string();
assert!(err.contains("base_dir"), "debe explicar la falta de base_dir: {err}");
}
@@ -1477,8 +1477,8 @@ mod dep_hash_tests {
let d = tempfile::tempdir().unwrap();
let store = store_in(d.path());
write_recipe(d.path(), "solo", "s0", &[]);
let a = artifact_hash(&load(d.path(), "solo"), &store, &hammer_core::lab::LabFingerprint::for_tests()).unwrap();
let b = artifact_hash(&load(d.path(), "solo"), &store, &hammer_core::lab::LabFingerprint::for_tests()).unwrap();
let a = artifact_hash(&load(d.path(), "solo"), &store, &takana_core::lab::LabFingerprint::for_tests()).unwrap();
let b = artifact_hash(&load(d.path(), "solo"), &store, &takana_core::lab::LabFingerprint::for_tests()).unwrap();
assert_eq!(a, b, "sin deps el hash es estable");
}
}
@@ -18,7 +18,7 @@ use std::sync::{Arc, Mutex};
const TAIL_LINES: usize = 50;
/// Fallo de una fase de build, con la cola del log del lab si la pudimos capturar. Viaja
/// dentro de `hammer_core::Error::Other(anyhow)`; el bus la recupera por `downcast_ref` para
/// dentro de `takana_core::Error::Other(anyhow)`; el bus la recupera por `downcast_ref` para
/// poblar `Event::BuildFailed.log_tail`. Ver `docs/07-agent-bus.md`.
#[derive(Debug, Clone)]
pub struct BuildFailure {
@@ -41,9 +41,9 @@ impl std::error::Error for BuildFailure {}
impl BuildFailure {
/// Recupera un `BuildFailure` de un error de hammer-core, si lo lleva dentro. El bus lo
/// usa para separar `reason` de `log_tail`.
pub fn from_error(e: &hammer_core::Error) -> Option<&BuildFailure> {
pub fn from_error(e: &takana_core::Error) -> Option<&BuildFailure> {
match e {
hammer_core::Error::Other(a) => a.downcast_ref::<BuildFailure>(),
takana_core::Error::Other(a) => a.downcast_ref::<BuildFailure>(),
_ => None,
}
}
@@ -87,8 +87,8 @@ impl Sandbox {
/// Ejecuta `cmd` dentro del sandbox bajo `sh -c`. Hace *tee* de stdout/stderr al padre
/// (para no perder el streaming en vivo) y a la vez retiene las últimas [`TAIL_LINES`]
/// líneas; si el exit code es != 0, devuelve un [`BuildFailure`] con esa cola en
/// `log_tail`, envuelto en `hammer_core::Error::Other`.
pub fn run(&self, cmd: &str) -> hammer_core::Result<()> {
/// `log_tail`, envuelto en `takana_core::Error::Other`.
pub fn run(&self, cmd: &str) -> takana_core::Result<()> {
// harkaq (SDD 16): sin HARKAQ=1 esto es `None` y todo lo de abajo es el camino de
// siempre — mismos args de bwrap, mismo entorno, ningún proceso extra. Inerte por
// requisito: hay 700+ artefactos sellados cuyo hash no puede moverse por encender un
@@ -109,7 +109,7 @@ impl Sandbox {
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.map_err(|e| hammer_core::Error::Other(anyhow::anyhow!("spawn bwrap: {e}")))?;
.map_err(|e| takana_core::Error::Other(anyhow::anyhow!("spawn bwrap: {e}")))?;
// Ring buffer compartido por ambos pumps: la cola combinada de stdout+stderr.
let tail = Arc::new(Mutex::new(VecDeque::<String>::with_capacity(TAIL_LINES)));
@@ -120,7 +120,7 @@ impl Sandbox {
let status = child
.wait()
.map_err(|e| hammer_core::Error::Other(anyhow::anyhow!("wait bwrap: {e}")))?;
.map_err(|e| takana_core::Error::Other(anyhow::anyhow!("wait bwrap: {e}")))?;
// Aseguramos drenar ambos streams antes de leer la cola.
let _ = h_out.join();
let _ = h_err.join();
@@ -148,7 +148,7 @@ impl Sandbox {
"build phase falló (exit {}): {cmd}",
status.code().map(|c| c.to_string()).unwrap_or_else(|| "?".into())
);
return Err(hammer_core::Error::Other(anyhow::Error::new(BuildFailure {
return Err(takana_core::Error::Other(anyhow::Error::new(BuildFailure {
reason,
log_tail: tail_str,
})));
@@ -160,7 +160,7 @@ impl Sandbox {
/// exit code en vez de fallar en != 0 — el caller decide contra lo esperado. El stderr se teea al
/// padre en vivo (logging), como en `run`. Es el motor de ejecución del checker de evidencia (H1b):
/// correr un `cmd` en el sandbox reproducible y comparar (exit, blake3(stdout)) contra lo declarado.
pub fn run_capture(&self, cmd: &str) -> hammer_core::Result<CmdOutcome> {
pub fn run_capture(&self, cmd: &str) -> takana_core::Result<CmdOutcome> {
let args = self.bwrap_args(cmd);
tracing::debug!(?args, "bwrap (capture)");
let mut child = Command::new("bwrap")
@@ -169,7 +169,7 @@ impl Sandbox {
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.map_err(|e| hammer_core::Error::Other(anyhow::anyhow!("spawn bwrap: {e}")))?;
.map_err(|e| takana_core::Error::Other(anyhow::anyhow!("spawn bwrap: {e}")))?;
let mut out = child.stdout.take().expect("stdout piped");
let err = child.stderr.take().expect("stderr piped");
@@ -184,7 +184,7 @@ impl Sandbox {
let status = child
.wait()
.map_err(|e| hammer_core::Error::Other(anyhow::anyhow!("wait bwrap: {e}")))?;
.map_err(|e| takana_core::Error::Other(anyhow::anyhow!("wait bwrap: {e}")))?;
let stdout = h_out.join().unwrap_or_default();
let _ = h_err.join();
@@ -526,17 +526,17 @@ fn spawn_pump<R: std::io::Read + Send + 'static>(
}
/// Errores tempranos: comprueba que las rutas base existen antes de invocar el build.
pub fn ensure_layout(rootfs: &Path, zig_dir: &Path) -> hammer_core::Result<()> {
pub fn ensure_layout(rootfs: &Path, zig_dir: &Path) -> takana_core::Result<()> {
// bin/sh en Alpine es un symlink absoluto a /bin/busybox, que desde el host no resuelve.
// Comprobamos el archivo regular real para no engañarnos.
if !rootfs.join("bin/busybox").is_file() {
return Err(hammer_core::Error::Other(anyhow::anyhow!(
return Err(takana_core::Error::Other(anyhow::anyhow!(
"rootfs inválido: no encuentro {}/bin/busybox — ¿corriste el bootstrap de Alpine?",
rootfs.display()
)));
}
if !zig_dir.join("zig").exists() {
return Err(hammer_core::Error::Other(anyhow::anyhow!(
return Err(takana_core::Error::Other(anyhow::anyhow!(
"zig no encontrado en {}/zig — ¿instalaste zig en .dev-fs/tools/zig?",
zig_dir.display()
)));
@@ -597,11 +597,11 @@ pub const RANLIB_WRAPPER: &str = "ranlib";
/// (ejecutable estático / `.a`) el wrapper es TRANSPARENTE ⇒ byte-idéntico a `zig cc`, y el resto del
/// árbol sigue estático. El ArtifactHash se deriva de inputs (receta+deps), no de los bytes ⇒ este
/// cambio NO re-hashea nada sellado; sólo destraba builds nuevos con enlace dinámico irreducible.
fn write_cc_wrappers(rootfs: &Path) -> hammer_core::Result<()> {
fn write_cc_wrappers(rootfs: &Path) -> takana_core::Result<()> {
use std::os::unix::fs::PermissionsExt;
let bindir = rootfs.join("usr/local/bin");
std::fs::create_dir_all(&bindir).map_err(|e| {
hammer_core::Error::Other(anyhow::anyhow!("no pude crear {}: {e}", bindir.display()))
takana_core::Error::Other(anyhow::anyhow!("no pude crear {}: {e}", bindir.display()))
})?;
// `case " $* "` detecta como palabra los marcadores de "este link es dinámico": -shared,
// -rdynamic, --export-dynamic, o un shared object en la línea (`…​.so ` final o `…​.so.N` versionado).
@@ -636,10 +636,10 @@ fn write_cc_wrappers(rootfs: &Path) -> hammer_core::Result<()> {
for (name, driver) in [(CC_WRAPPER, "zig cc"), (CXX_WRAPPER, "zig c++")] {
let path = bindir.join(name);
std::fs::write(&path, body(driver)).map_err(|e| {
hammer_core::Error::Other(anyhow::anyhow!("no pude escribir {}: {e}", path.display()))
takana_core::Error::Other(anyhow::anyhow!("no pude escribir {}: {e}", path.display()))
})?;
std::fs::set_permissions(&path, std::fs::Permissions::from_mode(0o755)).map_err(|e| {
hammer_core::Error::Other(anyhow::anyhow!("no pude chmod {}: {e}", path.display()))
takana_core::Error::Other(anyhow::anyhow!("no pude chmod {}: {e}", path.display()))
})?;
}
@@ -658,10 +658,10 @@ fn write_cc_wrappers(rootfs: &Path) -> hammer_core::Result<()> {
"#!/bin/sh\n # Interpuesto por hammer: fuerza archivos deterministas (sin hora de pared en la\n # cabecera `ar`). Sin esto dos builds idénticos sellan bytes distintos. Ver\n # sandbox.rs::AR_WRAPPER.\n [ -x /usr/bin/{tool} ] || exit 127\n exec /usr/bin/{tool} -D \"$@\"\n"
);
std::fs::write(&path, body).map_err(|e| {
hammer_core::Error::Other(anyhow::anyhow!("no pude escribir {}: {e}", path.display()))
takana_core::Error::Other(anyhow::anyhow!("no pude escribir {}: {e}", path.display()))
})?;
std::fs::set_permissions(&path, std::fs::Permissions::from_mode(0o755)).map_err(|e| {
hammer_core::Error::Other(anyhow::anyhow!("no pude chmod {}: {e}", path.display()))
takana_core::Error::Other(anyhow::anyhow!("no pude chmod {}: {e}", path.display()))
})?;
}
Ok(())
@@ -688,7 +688,7 @@ mod tests {
reason: "exit 2".into(),
log_tail: Some("undefined reference to `foo'".into()),
};
let err = hammer_core::Error::Other(anyhow::Error::new(bf));
let err = takana_core::Error::Other(anyhow::Error::new(bf));
let recovered = BuildFailure::from_error(&err).expect("debe recuperarse");
assert_eq!(recovered.reason, "exit 2");
assert_eq!(
@@ -699,7 +699,7 @@ mod tests {
#[test]
fn from_error_none_for_plain_errors() {
let err = hammer_core::Error::Recipe("nope".into());
let err = takana_core::Error::Recipe("nope".into());
assert!(BuildFailure::from_error(&err).is_none());
}
@@ -1,4 +1,4 @@
//! Puente entre `hammer_core::Mutation::SourcePatch` y el laboratorio.
//! Puente entre `takana_core::Mutation::SourcePatch` y el laboratorio.
//!
//! 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.
@@ -9,7 +9,7 @@
use std::path::{Path, PathBuf};
use hammer_core::{
use takana_core::{
swm::{Mutation, SwmBuild},
ArtifactHash, Compiler, LinkMode, Recipe, Store,
};
@@ -27,7 +27,7 @@ pub fn build_source_patch(
store: &Store,
scratch_root: Option<&Path>,
name: Option<&str>,
) -> hammer_core::Result<ArtifactHash> {
) -> takana_core::Result<ArtifactHash> {
let catalog_dir = catalog_dir_for(cfg, scratch_root);
// El NOMBRE de la receta sintetizada decide el sufijo del store (`<hash>-<name>`) Y la
// clave de cache-hit (`store.has(hash, name)`). Si quien llama conoce el nombre del paquete
@@ -47,7 +47,7 @@ pub fn build_source_patch(
.strip_prefix("b3:")
.expect("of_inputs siempre devuelve 'b3:'");
if want_hex != got_hex {
return Err(hammer_core::Error::Recipe(format!(
return Err(takana_core::Error::Recipe(format!(
"expected_hash no coincide: declarado=b3:{want_hex}, obtenido={hash}"
)));
}
@@ -72,7 +72,7 @@ pub fn recipe_from_source_patch(
mutation: &Mutation,
name: Option<&str>,
catalog_dir: &Path,
) -> hammer_core::Result<Recipe> {
) -> takana_core::Result<Recipe> {
let (repo, commit, tarball, sha256, strip_components, patch, patch_url, build_cfg, target_bin, deps, evidence, slots) =
match mutation {
Mutation::SourcePatch {
@@ -83,33 +83,33 @@ pub fn recipe_from_source_patch(
target_bin, deps, evidence, slots,
),
_ => {
return Err(hammer_core::Error::Recipe(
return Err(takana_core::Error::Recipe(
"recipe_from_source_patch: la mutación no es 'source_patch'".into(),
));
}
};
// git xor tarball — misma regla que el schema (`swm_source_kind`).
let source_kind = hammer_core::swm::swm_source_kind(
let source_kind = takana_core::swm::swm_source_kind(
repo.as_deref(),
commit.as_deref(),
tarball.as_deref(),
sha256.as_deref(),
)
.map_err(hammer_core::Error::Recipe)?;
.map_err(takana_core::Error::Recipe)?;
std::fs::create_dir_all(catalog_dir)?;
// Clave estable del origen para nombrar el patch a disco: el commit (git) o el sha256
// (tarball). Idéntica reaplicación ⇒ mismo nombre ⇒ hash de receta determinista.
let source_key: &str = match &source_kind {
hammer_core::SourceKind::Git { commit, .. } => commit,
hammer_core::SourceKind::Tarball { sha256, .. } => sha256,
takana_core::SourceKind::Git { commit, .. } => commit,
takana_core::SourceKind::Tarball { sha256, .. } => sha256,
// Inalcanzable por construcción —`swm_source_kind` sólo produce Git o Tarball— pero se
// escribe como error y no como `unreachable!`: esto es una librería, y un panic aquí
// mataría al llamador por una receta mal formada en vez de decírselo.
hammer_core::SourceKind::Dir { .. } => {
return Err(hammer_core::Error::Recipe(
takana_core::SourceKind::Dir { .. } => {
return Err(takana_core::Error::Recipe(
"un .swm no puede llevar un source por directorio".into(),
))
}
@@ -162,31 +162,31 @@ fn derive_name(target_bin: &str) -> String {
#[allow(clippy::too_many_arguments)]
fn synthesize_recipe(
name: &str,
source: &hammer_core::SourceKind<'_>,
source: &takana_core::SourceKind<'_>,
build_cfg: &SwmBuild,
strip_components: Option<usize>,
deps: &hammer_core::Deps,
evidence: &hammer_core::Evidence,
slots: &hammer_core::Slots,
deps: &takana_core::Deps,
evidence: &takana_core::Evidence,
slots: &takana_core::Slots,
patches: Vec<String>,
base_dir: &Path,
) -> hammer_core::Result<Recipe> {
) -> takana_core::Result<Recipe> {
let compiler = parse_compiler(&build_cfg.compiler)?;
let link = parse_link(&build_cfg.link)?;
// Sección `[source]` y sufijo de versión según el modo de origen. El validador de
// `Source::kind` ya garantiza git xor tarball; acá sólo emitimos el TOML correspondiente.
let (version_key, source_block) = match source {
hammer_core::SourceKind::Git { repo, commit } => (
takana_core::SourceKind::Git { repo, commit } => (
*commit,
format!("repo = \"{repo}\"\ncommit = \"{commit}\"\n"),
),
hammer_core::SourceKind::Tarball { url, sha256 } => (
takana_core::SourceKind::Tarball { url, sha256 } => (
*sha256,
format!("tarball = \"{url}\"\nsha256 = \"{sha256}\"\n"),
),
// Ver la nota del `source_key` de arriba: un `.swm` no puede traer un `dir`.
hammer_core::SourceKind::Dir { .. } => {
return Err(hammer_core::Error::Recipe(
takana_core::SourceKind::Dir { .. } => {
return Err(takana_core::Error::Recipe(
"un .swm no puede llevar un source por directorio".into(),
))
}
@@ -241,22 +241,22 @@ flags = []
Ok(recipe)
}
fn parse_compiler(s: &str) -> hammer_core::Result<Compiler> {
fn parse_compiler(s: &str) -> takana_core::Result<Compiler> {
match s {
"zig-cc" => Ok(Compiler::ZigCc),
"clang" => Ok(Compiler::Clang),
"gcc" => Ok(Compiler::Gcc),
other => Err(hammer_core::Error::Recipe(format!(
other => Err(takana_core::Error::Recipe(format!(
"compiler desconocido: '{other}' (esperado zig-cc/clang/gcc)"
))),
}
}
fn parse_link(s: &str) -> hammer_core::Result<LinkMode> {
fn parse_link(s: &str) -> takana_core::Result<LinkMode> {
match s {
"static" => Ok(LinkMode::Static),
"dynamic" => Ok(LinkMode::Dynamic),
other => Err(hammer_core::Error::Recipe(format!(
other => Err(takana_core::Error::Recipe(format!(
"link desconocido: '{other}' (esperado static/dynamic)"
))),
}
@@ -284,9 +284,9 @@ logo = "b3:azul"
[slots.requires]
"wayland-protocol" = "b3:stock"
"#;
let recipe = hammer_core::Recipe::from_toml(toml).unwrap();
let recipe = takana_core::Recipe::from_toml(toml).unwrap();
let swm =
hammer_core::Swm::from_recipe(&recipe, "/usr/bin/tema", None, None, "2026-06-06").unwrap();
takana_core::Swm::from_recipe(&recipe, "/usr/bin/tema", None, None, "2026-06-06").unwrap();
// El .swm YAML debe llevar el bloque slots.
let yaml = swm.to_yaml().unwrap();
assert!(yaml.contains("slots"), "el .swm debe transportar slots: {yaml}");
@@ -327,15 +327,15 @@ logo = "b3:azul"
let d = tempfile::tempdir().unwrap();
let r = synthesize_recipe(
"grep",
&hammer_core::SourceKind::Git {
&takana_core::SourceKind::Git {
repo: "git://example/grep.git",
commit: "a1b2c3d4e5f6a7b8c9",
},
&fake_swm_build(),
None,
&hammer_core::Deps::default(),
&hammer_core::Evidence::default(),
&hammer_core::Slots::default(),
&takana_core::Deps::default(),
&takana_core::Evidence::default(),
&takana_core::Slots::default(),
vec![],
d.path(),
)
@@ -344,7 +344,7 @@ logo = "b3:azul"
assert!(r.version.starts_with("swm-a1b2c3d4"));
assert_eq!(r.build.flags, vec!["--enable-foo"]);
match r.source.kind().unwrap() {
hammer_core::SourceKind::Git { repo, commit } => {
takana_core::SourceKind::Git { repo, commit } => {
assert_eq!(repo, "git://example/grep.git");
assert_eq!(commit, "a1b2c3d4e5f6a7b8c9");
}
@@ -357,15 +357,15 @@ logo = "b3:azul"
let d = tempfile::tempdir().unwrap();
let r = synthesize_recipe(
"grep",
&hammer_core::SourceKind::Tarball {
&takana_core::SourceKind::Tarball {
url: "https://ftp.gnu.org/gnu/grep/grep-3.11.tar.gz",
sha256: "abcdef0123456789",
},
&fake_swm_build(),
None,
&hammer_core::Deps::default(),
&hammer_core::Evidence::default(),
&hammer_core::Slots::default(),
&takana_core::Deps::default(),
&takana_core::Evidence::default(),
&takana_core::Slots::default(),
vec![],
d.path(),
)
@@ -373,7 +373,7 @@ logo = "b3:azul"
assert_eq!(r.name, "grep");
assert!(r.version.starts_with("swm-abcdef012345"));
match r.source.kind().unwrap() {
hammer_core::SourceKind::Tarball { url, sha256 } => {
takana_core::SourceKind::Tarball { url, sha256 } => {
assert_eq!(url, "https://ftp.gnu.org/gnu/grep/grep-3.11.tar.gz");
assert_eq!(sha256, "abcdef0123456789");
}
@@ -9,8 +9,8 @@
use std::path::{Path, PathBuf};
use std::process::Command;
use hammer_build::{build, BuildConfig};
use hammer_core::{Recipe, Store};
use takana_build::{build, BuildConfig};
use takana_core::{Recipe, Store};
const HELLO_C: &str = r#"
#include <stdio.h>
@@ -11,8 +11,8 @@
use std::path::PathBuf;
use std::process::Command;
use hammer_build::{build, run_hydrate, BuildConfig};
use hammer_core::{LinkMode, Recipe, Store};
use takana_build::{build, run_hydrate, BuildConfig};
use takana_core::{LinkMode, Recipe, Store};
const RECIPE_TOML: &str = r#"
name = "grep"
@@ -1,7 +1,7 @@
//! Integration test del pipeline completo de Fase 0:
//! 1. Inicializa un repo git local con un `hello.c`.
//! 2. Escribe una receta TOML que apunta a ese repo con phases override.
//! 3. Llama a `hammer_build::build` con un store/work temporales.
//! 3. Llama a `takana_build::build` con un store/work temporales.
//! 4. Verifica que el artefacto sellado contiene `usr/bin/hello`, es ELF estático y
//! se ejecuta dentro del sandbox imprimiendo el output esperado.
//!
@@ -12,8 +12,8 @@
use std::path::{Path, PathBuf};
use std::process::Command;
use hammer_build::{build, BuildConfig};
use hammer_core::{Recipe, Store};
use takana_build::{build, BuildConfig};
use takana_core::{Recipe, Store};
const HELLO_C: &str = r#"
#include <stdio.h>
@@ -4,7 +4,7 @@
//! - Genero un tarball en un tempdir con contenido conocido.
//! - Calculo su sha256 real con la misma crate (`sha2`) que usa el fetch.
//! - Sirvo el tarball vía URL `file://` (curl la maneja nativamente).
//! - Llamo a `hammer_build::fetch::fetch` con una receta TOML que apunta a esa URL.
//! - Llamo a `takana_build::fetch::fetch` con una receta TOML que apunta a esa URL.
//!
//! Cubre: happy path con strip_components, mismatch de sha256 no envenena la caché, reuso
//! de caché en segunda llamada, strip_components = 0 vs 1.
@@ -12,8 +12,8 @@
use std::path::{Path, PathBuf};
use std::process::Command;
use hammer_build::fetch::fetch;
use hammer_core::Recipe;
use takana_build::fetch::fetch;
use takana_core::Recipe;
use sha2::{Digest, Sha256};
/// Crea `<dir>/inner/{a.txt,sub/b.txt}` y empaqueta `inner/` en `<dir>/payload.tar.gz`.
@@ -12,8 +12,8 @@
use std::path::Path;
use hammer_build::hydrate::hydrate;
use hammer_core::LinkMode;
use takana_build::hydrate::hydrate;
use takana_core::LinkMode;
/// Artefacto que trae `usr/share/pkg` como symlink a `destino`.
fn artefacto_con_symlink(dir: &Path, destino: &Path) {
@@ -1,5 +1,5 @@
[package]
name = "hammer-cli"
name = "takana-cli"
version.workspace = true
edition.workspace = true
license.workspace = true
@@ -23,17 +23,17 @@ path = "src/main.rs"
default = []
# Activa el traductor LLM real (Claude API) detrás del flag `hammer ai --llm`.
# Sin esta feature, `--llm` falla con un mensaje claro pidiendo recompilar.
llm-claude = ["hammer-agent/llm-claude"]
llm-claude = ["takana-agent/llm-claude"]
[dependencies]
hammer-core.workspace = true
hammer-build.workspace = true
hammer-bootstrap.workspace = true
hammer-mirror.workspace = true
hammer-upgrade.workspace = true
hammer-overlay.workspace = true
hammer-journal.workspace = true
hammer-agent.workspace = true
takana-core.workspace = true
takana-build.workspace = true
takana-bootstrap.workspace = true
takana-mirror.workspace = true
takana-upgrade.workspace = true
takana-overlay.workspace = true
takana-journal.workspace = true
takana-agent.workspace = true
serde.workspace = true
serde_json.workspace = true
toml.workspace = true
@@ -334,7 +334,7 @@ package() {
#[test]
fn extracts_source_patches_deps_phases() {
let toml = recipe_from_apkbuild(APKBUILD_PATCHED).unwrap();
let recipe = hammer_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, "foo");
assert_eq!(recipe.version, "2.5");
// tarball con $pkgver expandido:
@@ -378,7 +378,7 @@ package() {
#[test]
fn no_patches_is_empty() {
let apk = "pkgname=zlib\npkgver=1.3\nsource=\"https://zlib.net/zlib-$pkgver.tar.gz\"\nsha512sums=\"x z\"\n";
let recipe = hammer_core::Recipe::from_toml(&recipe_from_apkbuild(apk).unwrap()).unwrap();
let recipe = takana_core::Recipe::from_toml(&recipe_from_apkbuild(apk).unwrap()).unwrap();
assert_eq!(recipe.name, "zlib");
assert!(recipe.source.patches.is_empty());
assert_eq!(recipe.source.tarball.as_deref(), Some("https://zlib.net/zlib-1.3.tar.gz"));
@@ -8,8 +8,8 @@ use std::path::{Path, PathBuf};
use anyhow::{Context, Result};
use clap::Subcommand;
use hammer_core::kernel::kconfig::KconfigTree;
use hammer_core::kernel::KernelConfig;
use takana_core::kernel::kconfig::KconfigTree;
use takana_core::kernel::KernelConfig;
use serde::Serialize;
/// Dónde busca el árbol de Kconfig si no se pasa `--kconfig`.
@@ -425,7 +425,7 @@ struct ClosureReport {
/// De la clausura, los que un humano puede marcar en `menuconfig`.
visible: usize,
/// `select` que entran desde fuera: reencienden algo de la clausura ignorando sus `depends on`.
leaks: Vec<hammer_core::kernel::SelectLeak>,
leaks: Vec<takana_core::kernel::SelectLeak>,
/// Símbolos distintos de la clausura alcanzados por esas fugas.
leaked_targets: usize,
#[serde(skip_serializing_if = "Option::is_none")]
@@ -447,7 +447,7 @@ struct FixpointReport {
/// Lo que hubo que apagar de más, con la ronda en que apareció.
added: Vec<(usize, String)>,
/// Fugas que sobrevivieron al tope de rondas. Vacío = cerró.
remaining_leaks: Vec<hammer_core::kernel::SelectLeak>,
remaining_leaks: Vec<takana_core::kernel::SelectLeak>,
}
/// Contraste contra un `.config` real: la validación §3 del SDD 22.
@@ -520,7 +520,7 @@ fn closure(
let on: Vec<String> = cl.iter().filter(|s| cfg.is_on(s)).cloned().collect();
let off = cl
.iter()
.filter(|s| matches!(cfg.get(s), Some(hammer_core::kernel::ConfigValue::No)))
.filter(|s| matches!(cfg.get(s), Some(takana_core::kernel::ConfigValue::No)))
.count();
let absent = cl.iter().filter(|s| cfg.get(s).is_none()).count();
Some(AgainstReport {
@@ -644,7 +644,7 @@ fn probe(
with_hw: bool,
json: bool,
) -> Result<()> {
use hammer_core::kernel::reverse::{BundleState, HwSignal};
use takana_core::kernel::reverse::{BundleState, HwSignal};
let cfg_path = match config {
Some(p) => p.to_path_buf(),
@@ -655,9 +655,9 @@ fn probe(
anyhow::bail!("{} no tiene un solo CONFIG_*: ¿es un .config?", cfg_path.display());
}
let tree = load_tree(kroot)?;
let cat = hammer_core::kernel::Catalog::load(catalog)?;
let hw = with_hw.then(hammer_core::kernel::Hardware::probe_local);
let rep = hammer_core::kernel::reverse::analyze(&cat, &tree, &cfg, hw.as_ref());
let cat = takana_core::kernel::Catalog::load(catalog)?;
let hw = with_hw.then(takana_core::kernel::Hardware::probe_local);
let rep = takana_core::kernel::reverse::analyze(&cat, &tree, &cfg, hw.as_ref());
if json {
println!("{}", serde_json::to_string_pretty(&rep)?);
@@ -793,7 +793,7 @@ struct BundleView {
closure_len: usize,
visible: usize,
unknown_symbols: Vec<String>,
undeclared_leaks: Vec<hammer_core::kernel::SelectLeak>,
undeclared_leaks: Vec<takana_core::kernel::SelectLeak>,
accepted_leaks: usize,
}
@@ -811,7 +811,7 @@ struct BundlesReport {
struct KnobView {
id: String,
title: String,
side: hammer_core::kernel::Side,
side: takana_core::kernel::Side,
enable: Vec<String>,
disable: Vec<String>,
#[serde(skip_serializing_if = "Option::is_none")]
@@ -821,7 +821,7 @@ struct KnobView {
fn bundles(kroot: &Path, catalog: &Path, check: bool, json: bool) -> Result<()> {
let tree = load_tree(kroot)?;
let cat = hammer_core::kernel::Catalog::load(catalog)?;
let cat = takana_core::kernel::Catalog::load(catalog)?;
let mut views = Vec::new();
for b in &cat.bundles {
@@ -932,17 +932,17 @@ fn bundles(kroot: &Path, catalog: &Path, check: bool, json: bool) -> Result<()>
println!("NIVEL 2 — política y rendimiento (ojo al lado del que cae cada perilla)");
for k in &rep.knobs {
let lado = match k.side {
hammer_core::kernel::Side::Kconfig => "kconfig",
hammer_core::kernel::Side::Recipe => "receta ",
takana_core::kernel::Side::Kconfig => "kconfig",
takana_core::kernel::Side::Recipe => "receta ",
};
let que = match k.side {
hammer_core::kernel::Side::Kconfig => {
takana_core::kernel::Side::Kconfig => {
let mut v: Vec<String> =
k.enable.iter().map(|s| format!("+{s}")).collect();
v.extend(k.disable.iter().map(|s| format!("-{s}")));
v.join(" ")
}
hammer_core::kernel::Side::Recipe => {
takana_core::kernel::Side::Recipe => {
k.recipe_field.clone().unwrap_or_default()
}
};
@@ -988,9 +988,9 @@ fn plan(
if bundle_ids.is_empty() && knob_ids.is_empty() {
anyhow::bail!("un plan sin bundles ni perillas es la receta base: pasá --bundle/--knob");
}
let base = hammer_core::Recipe::load_from_path(recipe_path)?;
let base = takana_core::Recipe::load_from_path(recipe_path)?;
let tree = load_tree(kroot)?;
let cat = hammer_core::kernel::Catalog::load(catalog)?;
let cat = takana_core::kernel::Catalog::load(catalog)?;
// Las recetas de kernel del repo NO son todas la misma versión (`linux`/`linux-metal` van por
// 6.16.12; `linux-metal-dual`/`linux-generic` por 7.1.2). Planear una contra el árbol de la otra
@@ -1015,7 +1015,7 @@ fn plan(
Some(_) => {}
}
let mut p = hammer_core::kernel::plan::make_plan(
let mut p = takana_core::kernel::plan::make_plan(
&base,
&recipe_path.display().to_string(),
&cat,
@@ -1027,14 +1027,14 @@ fn plan(
// El plan DETERMINA el artefacto (SDD 22 §1), así que el hash va en el plan: la UI puede decir
// «esto ya está construido y firmado» sin construir nada. Si las deps no están resueltas, se
// dice que no se pudo en vez de emitir un hash inventado.
let derived = hammer_core::kernel::plan::derive_recipe(&base, &p);
match hammer_core::Store::open(Path::new(store))
let derived = takana_core::kernel::plan::derive_recipe(&base, &p);
match takana_core::Store::open(Path::new(store))
.map_err(anyhow::Error::from)
.and_then(|s| {
let cfg = hammer_build::BuildConfig::from_env_or_defaults(s.root());
let lab = hammer_core::lab::LabFingerprint::from_rootfs(&cfg.rootfs)
let cfg = takana_build::BuildConfig::from_env_or_defaults(s.root());
let lab = takana_core::lab::LabFingerprint::from_rootfs(&cfg.rootfs)
.map_err(anyhow::Error::from)?;
hammer_build::artifact_hash(&derived, &s, &lab).map_err(anyhow::Error::from)
takana_build::artifact_hash(&derived, &s, &lab).map_err(anyhow::Error::from)
})
{
Ok(h) => p.artifact_hash = Some(h.to_string()),
@@ -1065,7 +1065,7 @@ fn plan(
.map(|d| d.display().to_string())
.unwrap_or_default(),
);
let cuerpo = hammer_core::kernel::plan::render_recipe_toml(&derived)?;
let cuerpo = takana_core::kernel::plan::render_recipe_toml(&derived)?;
std::fs::write(ro, format!("{cabecera}{cuerpo}"))
.with_context(|| format!("escribiendo {}", ro.display()))?;
}
@@ -1091,8 +1091,8 @@ fn plan(
println!("(se emiten RAÍCES: la clausura la calcula el olddefconfig del propio kernel)");
for s in &p.symbols {
let signo = match s.action {
hammer_core::kernel::plan::Action::Enable => "+",
hammer_core::kernel::plan::Action::Disable => "-",
takana_core::kernel::plan::Action::Enable => "+",
takana_core::kernel::plan::Action::Disable => "-",
};
println!(" {signo}{:<24}{} ({})", s.symbol, s.from, s.reason);
}
@@ -1121,7 +1121,7 @@ fn plan(
}
fn diff_back(plan_path: &Path, config: &Path, json: bool) -> Result<()> {
let p: hammer_core::kernel::Plan = serde_json::from_str(
let p: takana_core::kernel::Plan = serde_json::from_str(
&std::fs::read_to_string(plan_path)
.with_context(|| format!("leyendo {}", plan_path.display()))?,
)
@@ -1130,7 +1130,7 @@ fn diff_back(plan_path: &Path, config: &Path, json: bool) -> Result<()> {
if cfg.is_empty() {
anyhow::bail!("{} no tiene un solo CONFIG_*: ¿es un .config?", config.display());
}
let d = hammer_core::kernel::diff_back(&p, &cfg);
let d = takana_core::kernel::diff_back(&p, &cfg);
if json {
println!("{}", serde_json::to_string_pretty(&d)?);
@@ -1167,7 +1167,7 @@ fn diff_back(plan_path: &Path, config: &Path, json: bool) -> Result<()> {
// ---------------------------------------------------------------------------------------------
fn hw(out: Option<&Path>) -> Result<()> {
let h = hammer_core::kernel::Hardware::probe_local();
let h = takana_core::kernel::Hardware::probe_local();
let texto = serde_json::to_string_pretty(&h)?;
match out {
Some(o) => {
@@ -1201,7 +1201,7 @@ fn gate(
if plan_path.is_none() && config.is_none() {
anyhow::bail!("pasá --plan (qué apagaría) o --config (qué quedó): sin uno de los dos no hay nada que comparar");
}
let p: Option<hammer_core::kernel::Plan> = match plan_path {
let p: Option<takana_core::kernel::Plan> = match plan_path {
None => None,
Some(pp) => Some(
serde_json::from_str(
@@ -1210,9 +1210,9 @@ fn gate(
.with_context(|| format!("{} no es un plan de `hammer kernel plan`", pp.display()))?,
),
};
let cat = hammer_core::kernel::Catalog::load(catalog)?;
let cat = takana_core::kernel::Catalog::load(catalog)?;
let tree = load_tree(kroot)?;
let modmap = hammer_core::kernel::ModMap::parse(kroot)
let modmap = takana_core::kernel::ModMap::parse(kroot)
.with_context(|| format!("leyendo los Makefiles de {}", kroot.display()))?;
if modmap.is_empty() {
anyhow::bail!(
@@ -1223,11 +1223,11 @@ fn gate(
}
let hardware = match devices {
Some(d) => serde_json::from_str::<hammer_core::kernel::Hardware>(
Some(d) => serde_json::from_str::<takana_core::kernel::Hardware>(
&std::fs::read_to_string(d).with_context(|| format!("leyendo {}", d.display()))?,
)
.with_context(|| format!("{} no es un volcado de `hammer kernel hw`", d.display()))?,
None => hammer_core::kernel::Hardware::probe_local(),
None => takana_core::kernel::Hardware::probe_local(),
};
let r = match config {
@@ -1242,7 +1242,7 @@ fn gate(
anyhow::bail!("{} no tiene un solo CONFIG_*: ¿es un .config?", c.display());
}
println!("referente {} ({} símbolos)", base_path.display(), base.len());
hammer_core::kernel::gate::run_against_config(
takana_core::kernel::gate::run_against_config(
&base,
&nuevo,
&cat,
@@ -1253,7 +1253,7 @@ fn gate(
Some(&tree),
)?
}
None => hammer_core::kernel::gate::run(
None => takana_core::kernel::gate::run(
p.as_ref().expect("validado arriba"),
&cat,
&tree,
@@ -1312,7 +1312,7 @@ fn gate(
}
}
if r.verdict == hammer_core::kernel::Verdict::Bloquea {
if r.verdict == takana_core::kernel::Verdict::Bloquea {
anyhow::bail!("el gate bloquea");
}
Ok(())
@@ -1370,10 +1370,10 @@ fn deps_resuelven_desde(dir: &Path, deps: &[String]) -> bool {
/// Primero por convención (`<dir>/<artifact>.toml`) y sólo si eso no da, barriendo el directorio:
/// **el artefacto se nombra por el campo `name`, no por el fichero**, y en este corpus hay recetas
/// donde los dos difieren.
fn receta_de(artifact: &str, dirs: &[PathBuf]) -> Option<hammer_core::Recipe> {
fn receta_de(artifact: &str, dirs: &[PathBuf]) -> Option<takana_core::Recipe> {
for d in dirs {
let directo = d.join(format!("{artifact}.toml"));
if let Ok(r) = hammer_core::Recipe::load_from_path(&directo) {
if let Ok(r) = takana_core::Recipe::load_from_path(&directo) {
if r.name == artifact {
return Some(con_catalogo(r, dirs));
}
@@ -1382,7 +1382,7 @@ fn receta_de(artifact: &str, dirs: &[PathBuf]) -> Option<hammer_core::Recipe> {
for e in rd.flatten() {
let p = e.path();
if p.extension().is_some_and(|x| x == "toml") {
if let Ok(r) = hammer_core::Recipe::load_from_path(&p) {
if let Ok(r) = takana_core::Recipe::load_from_path(&p) {
if r.name == artifact {
return Some(con_catalogo(r, dirs));
}
@@ -1404,7 +1404,7 @@ fn receta_de(artifact: &str, dirs: &[PathBuf]) -> Option<hammer_core::Recipe> {
///
/// NO se toca si la receta trae `patches`: `base_dir` también los resuelve, y moverlo los rompería
/// en silencio. Ahí es mejor no saber la vigencia (se comprueba todo) que hashear otra cosa.
fn con_catalogo(mut r: hammer_core::Recipe, dirs: &[PathBuf]) -> hammer_core::Recipe {
fn con_catalogo(mut r: takana_core::Recipe, dirs: &[PathBuf]) -> takana_core::Recipe {
if r.deps.build.is_empty() || deps_resuelven_desde(&r.base_dir, &r.deps.build) {
return r;
}
@@ -1434,13 +1434,13 @@ fn con_catalogo(mut r: hammer_core::Recipe, dirs: &[PathBuf]) -> hammer_core::Re
/// lab de hoy — o sea que un kernel sellado con otro rootfs también sale superado, y es correcto
/// (SDD 25: el lab entra en el artefacto).
fn dirs_vigentes(
c: &hammer_core::kernel::Contract,
c: &takana_core::kernel::Contract,
dirs: &[PathBuf],
store: &str,
) -> Result<std::collections::BTreeMap<String, String>> {
let st = hammer_core::Store::open(Path::new(store))?;
let cfg = hammer_build::BuildConfig::from_env_or_defaults(st.root());
let lab = hammer_core::lab::LabFingerprint::from_rootfs(&cfg.rootfs)?;
let st = takana_core::Store::open(Path::new(store))?;
let cfg = takana_build::BuildConfig::from_env_or_defaults(st.root());
let lab = takana_core::lab::LabFingerprint::from_rootfs(&cfg.rootfs)?;
let mut out = std::collections::BTreeMap::new();
for t in &c.targets {
let Some(r) = receta_de(&t.artifact, dirs) else {
@@ -1451,7 +1451,7 @@ fn dirs_vigentes(
);
continue;
};
match hammer_build::artifact_hash(&r, &st, &lab) {
match takana_build::artifact_hash(&r, &st, &lab) {
Ok(h) => {
out.insert(t.artifact.clone(), h.store_dir_name(&r.name));
}
@@ -1464,10 +1464,10 @@ fn dirs_vigentes(
#[derive(Serialize)]
struct ContractRun {
contract: String,
reports: Vec<hammer_core::kernel::ContractReport>,
reports: Vec<takana_core::kernel::ContractReport>,
/// Sellados que la receta de HOY ya no produce. Se informan y no bloquean: ver `dirs_vigentes`.
#[serde(default)]
superados: Vec<hammer_core::kernel::ContractReport>,
superados: Vec<takana_core::kernel::ContractReport>,
/// Configs que NO se comprobaron por no tener perfil declarado. No cuentan como aprobados.
sin_perfil: Vec<String>,
/// Objetivos del contrato cuya receta de hoy NO tiene ni un sellado en el store. No se
@@ -1486,8 +1486,8 @@ fn contract_cmd(
list: bool,
json: bool,
) -> Result<()> {
use hammer_core::kernel::contract::sealed_kernel_configs;
use hammer_core::kernel::Contract;
use takana_core::kernel::contract::sealed_kernel_configs;
use takana_core::kernel::Contract;
let c = Contract::load(contract)?;
@@ -1501,7 +1501,7 @@ fn contract_cmd(
println!("revisado contra kernel {r}");
}
println!();
let req = hammer_core::kernel::contract::required_by(&c);
let req = takana_core::kernel::contract::required_by(&c);
for cap in &c.capabilities {
let quien = req.get(cap.id.as_str()).map(|v| v.join(", ")).unwrap_or_default();
let quien = if quien.is_empty() { "".to_string() } else { quien };
@@ -1586,7 +1586,7 @@ fn contract_cmd(
};
for (label, artifact, path) in &objetivos {
let cfg = hammer_core::kernel::KernelConfig::parse(&read_maybe_gz(path)?);
let cfg = takana_core::kernel::KernelConfig::parse(&read_maybe_gz(path)?);
if cfg.is_empty() {
anyhow::bail!("{} no tiene un solo CONFIG_*: ¿es un .config?", path.display());
}
@@ -1634,7 +1634,7 @@ fn contract_cmd(
let bloquean = run
.reports
.iter()
.filter(|r| r.verdict == hammer_core::kernel::Verdict::Bloquea)
.filter(|r| r.verdict == takana_core::kernel::Verdict::Bloquea)
.count();
if bloquean > 0 {
anyhow::bail!("el contrato NO se cumple en {bloquean} kernel(es)");
@@ -1655,7 +1655,7 @@ fn imprimir_contrato(run: &ContractRun) {
println!("contrato {}", run.contract);
println!();
for r in &run.reports {
let marca = if r.verdict == hammer_core::kernel::Verdict::Bloquea { "" } else { "" };
let marca = if r.verdict == takana_core::kernel::Verdict::Bloquea { "" } else { "" };
println!("{marca} {}", r.config);
println!(" perfil {} ({} capacidad(es) miradas)", r.profile, r.checked);
if let Some(k) = &r.kernel {
@@ -1699,13 +1699,13 @@ fn imprimir_contrato(run: &ContractRun) {
let malos = run
.reports
.iter()
.filter(|r| r.verdict == hammer_core::kernel::Verdict::Bloquea)
.filter(|r| r.verdict == takana_core::kernel::Verdict::Bloquea)
.count();
if !run.superados.is_empty() {
let sup_malos = run
.superados
.iter()
.filter(|r| r.verdict == hammer_core::kernel::Verdict::Bloquea)
.filter(|r| r.verdict == takana_core::kernel::Verdict::Bloquea)
.count();
println!(
"· {} sellado(s) SUPERADO(s) por la receta de hoy — no bloquean, {} con faltas:",
File diff suppressed because it is too large Load Diff
@@ -417,7 +417,7 @@ mod tests {
let pkg: NixPkg = serde_json::from_str(json).unwrap();
let toml = to_recipe_toml(&pkg).unwrap();
// Parsea como Recipe válida de hammer.
let recipe = hammer_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.version, "2.12.1");
// mirror://gnu/ se traduce a una URL concreta que el fetch de hammer entiende.
@@ -438,7 +438,7 @@ mod tests {
"is_rust": true, "main_program": "rg"
}"#;
let pkg: NixPkg = serde_json::from_str(json).unwrap();
let recipe = hammer_core::Recipe::from_toml(&to_recipe_toml(&pkg).unwrap()).unwrap();
let recipe = takana_core::Recipe::from_toml(&to_recipe_toml(&pkg).unwrap()).unwrap();
assert_eq!(recipe.build.flags, vec!["--bin", "rg"]);
let inst = recipe.build.phases.install.as_deref().unwrap();
assert!(inst.contains("cp target/release/rg /out/usr/bin/rg"), "{inst}");
@@ -456,7 +456,7 @@ mod tests {
}"#;
let pkg: NixPkg = serde_json::from_str(json).unwrap();
let toml = to_recipe_toml(&pkg).unwrap();
let recipe = hammer_core::Recipe::from_toml(&toml).unwrap();
let recipe = takana_core::Recipe::from_toml(&toml).unwrap();
assert!(recipe.deps.build.is_empty(), "Rust no emite deps activas: {:?}", recipe.deps.build);
assert!(toml.contains("# buildInputs de nix"), "deja provenance comentada: {toml}");
assert!(toml.contains("pcre2"), "lista los nombres en el comentario: {toml}");
@@ -471,7 +471,7 @@ mod tests {
"native_build_inputs": ["install-shell-files", "version-check-hook"]
}"#;
let pkg: NixPkg = serde_json::from_str(json).unwrap();
let recipe = hammer_core::Recipe::from_toml(&to_recipe_toml(&pkg).unwrap()).unwrap();
let recipe = takana_core::Recipe::from_toml(&to_recipe_toml(&pkg).unwrap()).unwrap();
// pcre2 sobrevive (paquete real); hooks/wrappers/shell-files y el toolchain rustc/cargo
// se filtran (rustc/cargo son el lab, no deps).
assert_eq!(recipe.deps.build, vec!["pcre2"]);
@@ -503,7 +503,7 @@ mod tests {
}"#;
let pkg: NixPkg = serde_json::from_str(json).unwrap();
let toml = to_recipe_toml(&pkg).unwrap();
let recipe = hammer_core::Recipe::from_toml(&toml).unwrap();
let recipe = takana_core::Recipe::from_toml(&toml).unwrap();
assert_eq!(recipe.source.repo.as_deref(), Some("https://github.com/BurntSushi/ripgrep"));
assert_eq!(
recipe.source.commit.as_deref(),
@@ -293,7 +293,7 @@ fn pull(
download_to_file(url, &archive)?;
// El corte. Si el contenido no es el que dice ser, se ABORTA — no se avisa y se sigue.
hammer_build::download::verify_sha256(&archive, &sha).inspect_err(|_| {
takana_build::download::verify_sha256(&archive, &sha).inspect_err(|_| {
std::fs::remove_file(&archive).ok();
})?;
let archive_bytes = std::fs::metadata(&archive)?.len();
@@ -636,7 +636,7 @@ fn write_manifest(dir: &Path, m: &ImageManifest) -> Result<()> {
// importada del store y la traída con `pull` serían dos imágenes distintas con los mismos bytes, y
// las instancias de dos máquinas dejarían de coincidir — justo lo que el pin existe para evitar.
fn import(root: &Path, store: &Path, hash: &str, copiar: bool) -> Result<()> {
let store = hammer_core::store::Store::open(store)
let store = takana_core::store::Store::open(store)
.map_err(|e| anyhow::anyhow!("abriendo el store: {e}"))?;
let art = store.find_by_hash(hash)
.map_err(|e| anyhow::anyhow!("{e}"))?;
@@ -8,7 +8,7 @@
use std::process::Command;
use base64::{engine::general_purpose::STANDARD, Engine as _};
use hammer_core::ArtifactHash;
use takana_core::ArtifactHash;
#[test]
fn install_aborta_ante_colision_de_fichero_y_force_slots_la_supera() {
@@ -10,9 +10,9 @@ use std::collections::BTreeMap;
use std::path::Path;
use base64::{engine::general_purpose::STANDARD, Engine as _};
use hammer_core::apply::{apply_config_edit, apply_file_drop, rebase_path, verify_content_hash};
use hammer_core::swm::{Base, BaseCompat, BaseRef, Mutation, Swm};
use hammer_core::ArtifactHash;
use takana_core::apply::{apply_config_edit, apply_file_drop, rebase_path, verify_content_hash};
use takana_core::swm::{Base, BaseCompat, BaseRef, Mutation, Swm};
use takana_core::ArtifactHash;
fn write(p: &Path, content: &str) {
if let Some(parent) = p.parent() {
@@ -1,5 +1,5 @@
[package]
name = "hammer-core"
name = "takana-core"
version.workspace = true
edition.workspace = true
license.workspace = true
@@ -41,7 +41,7 @@ pub enum Command {
/// Consulta de estado. `what` decide qué campos se usan:
/// - `"file"`: usa `path` (metadata del archivo).
/// - `"artifact"`: usa `name` o `path` (busca un hash en el store).
/// - `"expr"`: usa `expr`, una expresión del mini-lenguaje (ver `hammer_core::query`).
/// - `"expr"`: usa `expr`, una expresión del mini-lenguaje (ver `takana_core::query`).
Query {
what: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
@@ -162,9 +162,9 @@ pub struct Peer {
pub pid: i32,
}
/// [H1c] Forma serializable del `EvidenceReport` de `hammer_build` que viaja en `BuildReady`.
/// [H1c] Forma serializable del `EvidenceReport` de `takana_build` que viaja en `BuildReady`.
/// El lado del build (hammerd) ejecuta la evidencia y la adjunta; el orquestador la lee sin
/// depender de `hammer_build` (la separación agente-PROPONE / lab-CONSTRUYE se preserva).
/// depender de `takana_build` (la separación agente-PROPONE / lab-CONSTRUYE se preserva).
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct EvidenceVerdict {
/// Gate: `false` ⇒ al menos un check declarado falló ⇒ el orquestador no propone la mutación.
@@ -244,7 +244,7 @@ impl Swm {
/// Construye un `.swm` de un único `source_patch` a partir de una [`crate::Recipe`] del
/// corpus — la dirección **forward** que cierra la paquetería (Etapa F): una receta que el
/// sistema YA sabe construir se vuelve un paquete distribuible y reproducible-desde-fuente.
/// Es la inversa de `hammer_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
/// (relativos a `recipe.base_dir`) y pasa su texto ya concatenado en `patch_text`. El
@@ -1,5 +1,5 @@
[package]
name = "hammer-journal"
name = "takana-journal"
version.workspace = true
edition.workspace = true
license.workspace = true
@@ -8,7 +8,7 @@ repository.workspace = true
description = "El diario de mutaciones de hammer: append-only JSON-líneas sobre eventos del FHS."
[dependencies]
hammer-core.workspace = true
takana-core.workspace = true
anyhow.workspace = true
thiserror.workspace = true
tracing.workspace = true
@@ -5,7 +5,7 @@
//!
//! Esta crate es **lógica pura**: nada de fanotify ni de mounts. El watcher real vive en
//! `hammerd` y empuja eventos aquí vía `Journal::record`. El hook desde
//! `hammer_overlay::commit` también empuja por aquí.
//! `takana_overlay::commit` también empuja por aquí.
//!
//! Diseño rationale (SDD 05):
//! - El diario no bloquea acciones; sólo observa. Pero también acepta eventos "trazables"
@@ -1,5 +1,5 @@
[package]
name = "hammer-mirror"
name = "takana-mirror"
version.workspace = true
edition.workspace = true
license.workspace = true
@@ -8,7 +8,7 @@ repository.workspace = true
description = "Mirror del store content-addressed de hammer (E3): replica artefactos por hash, verifica of_tree en recepción."
[dependencies]
hammer-core.workspace = true
takana-core.workspace = true
anyhow.workspace = true
thiserror.workspace = true
tracing.workspace = true
@@ -20,7 +20,7 @@
use std::path::Path;
use hammer_core::{ArtifactHash, Store};
use takana_core::{ArtifactHash, Store};
use serde::{Deserialize, Serialize};
#[derive(Debug, thiserror::Error)]
@@ -28,7 +28,7 @@ pub enum Error {
#[error("io: {0}")]
Io(#[from] std::io::Error),
#[error("core: {0}")]
Core(#[from] hammer_core::Error),
Core(#[from] takana_core::Error),
#[error(
"integridad: el artefacto '{dir}' recibido hashea a {got} pero el índice anuncia {want} — \
transferencia corrupta o manipulada; NO se selló"
@@ -1,5 +1,5 @@
[package]
name = "hammer-overlay"
name = "takana-overlay"
version.workspace = true
edition.workspace = true
license.workspace = true
@@ -8,8 +8,8 @@ repository.workspace = true
description = "El overlay de experimentación de hammer: try/commit/discard/status sobre overlayfs."
[dependencies]
hammer-core.workspace = true
hammer-journal.workspace = true
takana-core.workspace = true
takana-journal.workspace = true
anyhow.workspace = true
thiserror.workspace = true
tracing.workspace = true
@@ -248,7 +248,7 @@ pub fn commit(id: &OverlayId, state_root: &Path) -> Result<CommitReport> {
pub fn commit_with_journal(
id: &OverlayId,
state_root: &Path,
journal: &hammer_journal::Journal,
journal: &takana_journal::Journal,
) -> Result<CommitReport> {
commit_inner(id, state_root, Some(journal))
}
@@ -256,7 +256,7 @@ pub fn commit_with_journal(
fn commit_inner(
id: &OverlayId,
state_root: &Path,
journal: Option<&hammer_journal::Journal>,
journal: Option<&takana_journal::Journal>,
) -> Result<CommitReport> {
let base = state_root.join(id.as_str());
let state_file = base.join("state.json");
@@ -295,11 +295,11 @@ fn commit_inner(
}
fn record_commit_to_journal(
j: &hammer_journal::Journal,
j: &takana_journal::Journal,
id: &OverlayId,
report: &CommitReport,
) {
use hammer_journal::{Actor, MutationEvent, MutationOp, Source};
use takana_journal::{Actor, MutationEvent, MutationOp, Source};
let actor = Actor {
source: Source::HammerCommit {
overlay: id.as_str().to_string(),
@@ -308,7 +308,7 @@ fn record_commit_to_journal(
pid: Some(std::process::id()),
uid: Some(unsafe { libc::getuid() }),
};
let ts = hammer_journal::now_rfc3339();
let ts = takana_journal::now_rfc3339();
for p in &report.copied {
let ev = MutationEvent {
ts: ts.clone(),
@@ -665,7 +665,7 @@ mod tests {
// No tocamos mount real: ejercitamos directamente la función privada que el commit
// delega para anotar al diario. Eso valida el contrato sin necesidad de overlayfs.
let d = tempfile::tempdir().unwrap();
let journal = hammer_journal::Journal::open(d.path().join("journal")).unwrap();
let journal = takana_journal::Journal::open(d.path().join("journal")).unwrap();
let report = CommitReport {
copied: vec![PathBuf::from("/etc/hosts"), PathBuf::from("/bin/grep")],
removed: vec![PathBuf::from("/bin/obsoleto")],
@@ -678,13 +678,13 @@ mod tests {
.iter()
.map(|e| (e.op, e.path.display().to_string()))
.collect();
assert!(ops_paths.contains(&(hammer_journal::MutationOp::Replace, "/etc/hosts".into())));
assert!(ops_paths.contains(&(hammer_journal::MutationOp::Replace, "/bin/grep".into())));
assert!(ops_paths.contains(&(hammer_journal::MutationOp::Delete, "/bin/obsoleto".into())));
assert!(ops_paths.contains(&(takana_journal::MutationOp::Replace, "/etc/hosts".into())));
assert!(ops_paths.contains(&(takana_journal::MutationOp::Replace, "/bin/grep".into())));
assert!(ops_paths.contains(&(takana_journal::MutationOp::Delete, "/bin/obsoleto".into())));
// Todos comparten el mismo source con overlay id.
for e in &events {
match &e.by.source {
hammer_journal::Source::HammerCommit { overlay, .. } => {
takana_journal::Source::HammerCommit { overlay, .. } => {
assert_eq!(overlay, "1234-1");
}
other => panic!("source inesperado: {other:?}"),
@@ -16,7 +16,7 @@
use std::path::{Path, PathBuf};
use std::process::Command;
use hammer_overlay::{commit, discard, status, try_overlay, OverlayId};
use takana_overlay::{commit, discard, status, try_overlay, OverlayId};
fn have_bwrap() -> bool {
Command::new("bwrap")
@@ -197,7 +197,7 @@ fn body_nested_stack() {
// --- guard LIFO: no se puede commitear la capa de abajo con la de arriba encima ---
match commit(&id1, &state) {
Err(hammer_overlay::Error::Shadowed { id, shadowed_by }) => {
Err(takana_overlay::Error::Shadowed { id, shadowed_by }) => {
assert_eq!(id, id1);
assert_eq!(shadowed_by, vec![id2.clone()]);
}
@@ -1,5 +1,5 @@
[package]
name = "hammer-recover"
name = "takana-recover"
version.workspace = true
edition.workspace = true
license.workspace = true
@@ -7,10 +7,13 @@ authors.workspace = true
repository.workspace = true
description = "Mini-binario de auto-recuperación de upgrades al arranque (E4 #4b): el /sbin/init lo corre para completar/deshacer un apply interrumpido antes de incarnar."
# El PAQUETE se renombra, el BINARIO no: `/usr/sbin/hammer-recover` está copiado en
# sistemas ya instalados y `hammer-live-install.sh` hornea un hook de arranque que lo
# invoca por ese nombre. Renombrar el binario rompe máquinas instaladas. Etapa 6.
[[bin]]
name = "hammer-recover"
path = "src/main.rs"
[dependencies]
hammer-upgrade.workspace = true
hammer-journal.workspace = true
takana-upgrade.workspace = true
takana-journal.workspace = true
@@ -34,7 +34,7 @@ fn main() {
let state = env_path("HAMMER_RECOVER_STATE", "/var/lib/hammer/upgrades");
let journal_dir = env_path("HAMMER_RECOVER_JOURNAL", "/var/lib/hammer/journal");
match hammer_upgrade::pending(&state) {
match takana_upgrade::pending(&state) {
Ok(None) => {
println!("hammer-recover: sin upgrade interrumpido");
return;
@@ -48,11 +48,11 @@ fn main() {
}
}
let journal = hammer_journal::Journal::open(&journal_dir).ok();
let journal = takana_journal::Journal::open(&journal_dir).ok();
let j = journal.as_ref();
if force_rollback {
match hammer_upgrade::recover(&store, &root, &state, j, true) {
match takana_upgrade::recover(&store, &root, &state, j, true) {
Ok(r) => println!("hammer-recover: DESHECHO (generación {} descartada)", r.generation),
Err(e) => eprintln!("hammer-recover: rollback falló: {e}"),
}
@@ -60,11 +60,11 @@ fn main() {
}
// Optimista: completar el upgrade. Si el roll-forward falla, deshacer ⇒ FHS consistente igual.
match hammer_upgrade::recover(&store, &root, &state, j, false) {
match takana_upgrade::recover(&store, &root, &state, j, false) {
Ok(r) => println!("hammer-recover: COMPLETADO (generación {})", r.generation),
Err(e) => {
eprintln!("hammer-recover: roll-forward falló ({e}); deshaciendo para dejar el FHS consistente");
match hammer_upgrade::recover(&store, &root, &state, j, true) {
match takana_upgrade::recover(&store, &root, &state, j, true) {
Ok(r) => println!("hammer-recover: DESHECHO tras fallo (generación {})", r.generation),
Err(e2) => eprintln!("hammer-recover: rollback de respaldo también falló: {e2}"),
}
@@ -1,5 +1,5 @@
[package]
name = "hammer-upgrade"
name = "takana-upgrade"
version.workspace = true
edition.workspace = true
license.workspace = true
@@ -8,8 +8,8 @@ repository.workspace = true
description = "Upgrades atómicos de hammer (E4): aplica un árbol Stage1/producto del store al root vivo con generaciones y rollback."
[dependencies]
hammer-core.workspace = true
hammer-journal.workspace = true
takana-core.workspace = true
takana-journal.workspace = true
anyhow.workspace = true
thiserror.workspace = true
tracing.workspace = true
@@ -22,8 +22,8 @@
use std::collections::HashMap;
use std::path::Path;
use hammer_core::ArtifactHash;
use hammer_journal::Journal;
use takana_core::ArtifactHash;
use takana_journal::Journal;
use serde::{Deserialize, Serialize};
use crate::{
@@ -3,7 +3,7 @@
//! Aplicar un árbol Stage1/producto **nuevo** al root vivo sin reinstalar, de forma atómica y con
//! **rollback** al árbol anterior. El árbol nuevo es un artefacto sellado del store (CAS BLAKE3); el
//! "apply" lo proyecta sobre el FHS vivo (`target_root`, normalmente `/`), registra cada cambio en el
//! [diario](hammer_journal) y deja una **generación** nueva. Si algo sale mal, `rollback` revierte
//! [diario](takana_journal) y deja una **generación** nueva. Si algo sale mal, `rollback` revierte
//! exactamente al árbol anterior.
//!
//! ## Modelo de generaciones (inspirado en NixOS, aplicado in-place al FHS)
@@ -35,8 +35,8 @@
use std::path::{Path, PathBuf};
use hammer_core::{ArtifactHash, Store};
use hammer_journal::{Actor, Journal, MutationEvent, MutationOp, Source};
use takana_core::{ArtifactHash, Store};
use takana_journal::{Actor, Journal, MutationEvent, MutationOp, Source};
use serde::{Deserialize, Serialize};
pub mod boot_graph;
@@ -48,9 +48,9 @@ pub enum Error {
#[error("io: {0}")]
Io(#[from] std::io::Error),
#[error("core: {0}")]
Core(#[from] hammer_core::Error),
Core(#[from] takana_core::Error),
#[error("journal: {0}")]
Journal(#[from] hammer_journal::Error),
Journal(#[from] takana_journal::Error),
#[error("json: {0}")]
Json(#[from] serde_json::Error),
#[error(
@@ -789,7 +789,7 @@ fn journal_change(journal: Option<&Journal>, tree_dir: &str, dst: &Path, op: Cha
ChangeOp::Removed => MutationOp::Delete,
};
let ev = MutationEvent {
ts: hammer_journal::now_rfc3339(),
ts: takana_journal::now_rfc3339(),
op: mop,
path: dst.to_path_buf(),
by: Actor {
+1 -1
View File
@@ -12,7 +12,7 @@ PKG="${1:?uso: alpine-import.sh <pkg> [main|community]}"
REPO="${2:-main}"
BRANCH="${ALPINE_BRANCH:-master}"
OUTDIR="${OUTDIR:-.}"
HAMMER="${HAMMER:-cargo run -q -p hammer-cli --}"
HAMMER="${HAMMER:-cargo run -q -p takana-cli --}"
BASE="https://gitlab.alpinelinux.org/alpine/aports/-/raw/${BRANCH}/${REPO}/${PKG}"
apk=$(curl -fsSL "${BASE}/APKBUILD") || { echo "no encontré ${REPO}/${PKG} en aports@${BRANCH}" >&2; exit 1; }
+1 -1
View File
@@ -218,7 +218,7 @@ else
# imprime (busca «LLD»/«GNU ld»/«GNU gold») y `--enable-lto=cross` necesita un linker con plugin
# LLVM; `llvm22-linker-tools` da el LLVMgold.so para GNU ld, pero el camino soportado por
# upstream —y el que usa el propio APKBUILD de Alpine con `--enable-linker=lld`— es lld.
# ⚠ DEUDA CONOCIDA: `lld` NO casa ningún prefijo de `TOOLCHAIN_PREFIXES` (hammer-core/src/lab.rs)
# ⚠ DEUDA CONOCIDA: `lld` NO casa ningún prefijo de `TOOLCHAIN_PREFIXES` (takana-core/src/lab.rs)
# ⇒ su versión NO entra en la huella del lab. Eso es lo que permite añadirlo HOY sin re-hashear
# los 837 artefactos sellados (medido: la huella no se movió), y a la vez es un agujero: dos labs
# con distinto lld pueden sellar bytes distintos en la misma dirección. Sólo expone a las recetas
+1 -1
View File
@@ -21,7 +21,7 @@ REPO="${REPO:-$(pwd)/dist/repo}"
STORE="${STORE:-./store}"
DISTRO="${DISTRO:-dev}"
BUILD_TIMEOUT="${BUILD_TIMEOUT:-90}"
HAMMER="cargo run -q -p hammer-cli --"
HAMMER="cargo run -q -p takana-cli --"
rm -rf "$REPO"
mkdir -p "$REPO"
+1 -1
View File
@@ -56,7 +56,7 @@ while [ "$i" -le "$N" ]; do
--exclude /dist --exclude /tandas --exclude /.dev-fs --exclude /.git --exclude /.scratch \
--exclude '*.png' --exclude '/content*' ./ "root@$ip:/opt/hammer/" >/dev/null 2>&1
hN=$($SSH "root@$ip" "export PATH=\$HOME/.cargo/bin:\$PATH; cd /opt/hammer && \
cargo build --release -q -p hammer-cli 2>/dev/null; \
cargo build --release -q -p takana-cli 2>/dev/null; \
timeout ${POR:-900} ./target/release/takana build $RECETA --store \$PWD/store 2>/dev/null \
| grep -oE '^b3:[a-f0-9]{64}' | head -1" 2>/dev/null | tr -d '\r')
echo "${hN:-FALLÓ}"
+1 -1
View File
@@ -228,7 +228,7 @@ fi
# granja podaba eso: `store-gc.sh` mira el store, la poda de caché de CI mira la caché de CI, y este
# tercer montón crecía sin dueño desde que el store se mudó al volumen. Se recuperaron 70 G.
#
# POR QUÉ ES SEGURO: no es una caché. Los dos caminos de `hammer-build/src/fetch.rs` borran el árbol
# POR QUÉ ES SEGURO: no es una caché. Los dos caminos de `takana-build/src/fetch.rs` borran el árbol
# y lo re-extraen en CADA build, sin una sola rama que reutilice ⇒ un árbol viejo no acelera nada y
# borrarlo no cuesta ni la re-extracción. Lo único que se pierde es el post-mortem del último build
# de esa receta, y por eso el suelo son 24 h y no cero.
+1 -1
View File
@@ -4,7 +4,7 @@
# ── POR QUÉ NO BASTA CON farm-sync ──────────────────────────────────────────────────────────────
# `farm-sync.sh` sube el código con `--exclude /.dev-fs`, a propósito: el lab es del entorno, no del
# repo. Eso estaba bien cuando el toolchain NO entraba en el hash. Desde el 2026-08-10 sí entra
# (`hammer-core/src/lab.rs`), así que un worker con su lab horneado en la golden —rustc 1.96— sella
# (`takana-core/src/lab.rs`), así que un worker con su lab horneado en la golden —rustc 1.96— sella
# en direcciones DISTINTAS a las del hub —1.97— y su trabajo no le sirve a nadie: no es que compile
# distinto, es que lo guarda en otra dirección y el hub nunca lo encuentra.
#
+1 -1
View File
@@ -83,7 +83,7 @@ EOF
./ "root@$ip:$REMOTE/" >/dev/null
$SSH "root@$ip" "MNT=/mnt/cosecha; mkdir -p \$MNT; mount $DEV \$MNT 2>/dev/null || (mkfs.ext4 -q -F $DEV && mount $DEV \$MNT); mkdir -p \$MNT/verdicts \$MNT/logs"
scripts/farm/harkaq-farm-setup.sh "$ip" >/dev/null 2>&1 || echo " (setup con avisos, sigo)"
$SSH "root@$ip" "export PATH=\$HOME/.cargo/bin:\$PATH; cd $REMOTE && cargo build --release -q -p hammer-cli 2>&1 | tail -1"
$SSH "root@$ip" "export PATH=\$HOME/.cargo/bin:\$PATH; cd $REMOTE && cargo build --release -q -p takana-cli 2>&1 | tail -1"
# Servicio con auto-shutdown (el script harkaq-campana-vol lo maneja).
$SSH "root@$ip" "cat > /etc/systemd/system/harkaq-vol.service <<UNIT
[Unit]
+1 -1
View File
@@ -34,7 +34,7 @@ DRY="${DRY:-0}"
SKIP_GUI="${SKIP_GUI:-1}"
GUI_RE='^(cairo|pango|gtk|harfbuzz|glib|gdk-pixbuf|libadwaita|gtksourceview|fribidi|graphene|fontconfig|adwaita|fcft|.*-hello|mirada|appstream|gnome)'
[ -x "$HAMMER" ] || { echo "no encuentro hammer en $HAMMER (cargo build --release -p hammer-cli)"; exit 1; }
[ -x "$HAMMER" ] || { echo "no encuentro hammer en $HAMMER (cargo build --release -p takana-cli)"; exit 1; }
deuda=""; skipped_gui=""
for f in $(grep -lE '^[[:space:]]*link[[:space:]]*=[[:space:]]*"static"' recipes/*.toml); do
+1 -1
View File
@@ -57,7 +57,7 @@ $SSH "root@$IP" "cd $REMOTE &&
echo \" runtime base: \$(grep -c '^ro ' /root/.cache/harkaq/base.policy) reglas + \$(grep -c '^# expect' /root/.cache/harkaq/base.policy) esperadas\""
echo "== 4/4 comprobando la cadena de evidencia de punta a punta"
$SSH "root@$IP" "cd $REMOTE && cargo build -q -p hammer-cli 2>/dev/null || true; ls target/debug/hammer >/dev/null 2>&1 && echo ' hammer-cli listo ✓' || echo ' OJO: falta target/debug/hammer (cargo build -p hammer-cli)'"
$SSH "root@$IP" "cd $REMOTE && cargo build -q -p takana-cli 2>/dev/null || true; ls target/debug/hammer >/dev/null 2>&1 && echo ' hammer-cli listo ✓' || echo ' OJO: falta target/debug/hammer (cargo build -p takana-cli)'"
cat <<EOF
+1 -1
View File
@@ -149,7 +149,7 @@ if [ -x "$HAMMER_MUSL" ]; then
cp "$HAMMER_MUSL" "$ROOT_TREE/usr/bin/hammer"; chmod 0755 "$ROOT_TREE/usr/bin/hammer"
echo " + CLI hammer (static musl) para el menú de arranque por grafo"
else
echo " ! sin hammer musl ($HAMMER_MUSL) ⇒ el disco no traerá el menú de arranque (cargo build --target x86_64-unknown-linux-musl -p hammer-cli)" >&2
echo " ! sin hammer musl ($HAMMER_MUSL) ⇒ el disco no traerá el menú de arranque (cargo build --target x86_64-unknown-linux-musl -p takana-cli)" >&2
fi
# /sbin/init: wrapper que emite un marcador a /dev/ttyS0 (sobrevive `quiet`, que suprime el printk
# kernel-side) y hace exec de arje-zero. Prueba positiva de que el switch_root llegó al init real.
+2 -2
View File
@@ -121,7 +121,7 @@ if [ "${INSTALLER:-0}" = 1 ]; then
RECOVER_BIN="$ROOT/target/x86_64-unknown-linux-musl/release/hammer-recover"
if [ ! -x "$RECOVER_BIN" ]; then
echo "==> compilando hammer-recover (static musl) para el payload"
cargo build --release --target x86_64-unknown-linux-musl -p hammer-recover --manifest-path "$ROOT/Cargo.toml" >/dev/null 2>&1
cargo build --release --target x86_64-unknown-linux-musl -p takana-recover --manifest-path "$ROOT/Cargo.toml" >/dev/null 2>&1
fi
[ -x "$RECOVER_BIN" ] || { echo "no pude construir hammer-recover (target musl?)" >&2; exit 1; }
install -m 0755 "$RECOVER_BIN" "$IPL/hammer-recover"
@@ -131,7 +131,7 @@ if [ "${INSTALLER:-0}" = 1 ]; then
HAMMER_BIN="$ROOT/target/x86_64-unknown-linux-musl/release/hammer"
if [ ! -x "$HAMMER_BIN" ]; then
echo "==> compilando hammer CLI (static musl) para el payload"
cargo build --release --target x86_64-unknown-linux-musl -p hammer-cli --manifest-path "$ROOT/Cargo.toml" >/dev/null 2>&1
cargo build --release --target x86_64-unknown-linux-musl -p takana-cli --manifest-path "$ROOT/Cargo.toml" >/dev/null 2>&1
fi
[ -x "$HAMMER_BIN" ] || { echo "no pude construir el CLI hammer (target musl?)" >&2; exit 1; }
install -m 0755 "$HAMMER_BIN" "$IPL/hammer"
+1 -1
View File
@@ -2,7 +2,7 @@
# lab-image.sh — empaqueta y publica el ROOTFS YA BOOTSTRAPEADO como imagen pineada por sha256.
#
# ── POR QUÉ EXISTE ──────────────────────────────────────────────────────────────────────────────
# Desde el 2026-08-10 el toolchain del lab entra en `hash_inputs` (ver `hammer-core/src/lab.rs`):
# Desde el 2026-08-10 el toolchain del lab entra en `hash_inputs` (ver `takana-core/src/lab.rs`):
# dos máquinas con distinto `rustc` sellan en direcciones distintas. Eso cierra un agujero real
# —antes sellaban bytes distintos en la MISMA dirección— pero abre uno operativo:
#
+2 -2
View File
@@ -4,12 +4,12 @@
# nixpkgs es el mayor set de recetas desde fuente; importamos la receta y hammer reconstruye.
#
# Requiere `nix` (con nix-command). Ej: scripts/nix-import.sh hello > recipes/hello.toml
# Env: NIXPKGS=<flakeref> (default nixpkgs), HAMMER=<cmd> (default cargo run -q -p hammer-cli --)
# Env: NIXPKGS=<flakeref> (default nixpkgs), HAMMER=<cmd> (default cargo run -q -p takana-cli --)
set -eu
ATTR="${1:?uso: nix-import.sh <attr-de-nixpkgs> (ej: hello, jq, ripgrep)}"
NIXPKGS="${NIXPKGS:-nixpkgs}"
HAMMER="${HAMMER:-cargo run -q -p hammer-cli --}"
HAMMER="${HAMMER:-cargo run -q -p takana-cli --}"
# NIX_STORE: si el /nix/store del sistema no es escribible (install single-user incompleta), apuntá
# a un store local, p.ej. NIX_STORE="local?root=$HOME/.nixstore".
STORE_OPT=""

Some files were not shown because too many files have changed in this diff Show More