H1b (proof-carrying recipes): checker swm-verify --evidence que ejecuta la evidencia
El verificador que corre cada check en el sandbox reproducible y falla ⇒ no proponer (SDD 15 §H1). Piezas: (1) hammer-core: EvidenceCheck::evaluate(exit, stdout) -> CheckOutcome (pura, testeada: exige expected_exit y, si hay, blake3(stdout)==expected_output) + ArtifactHash::of_bytes. (2) hammer-build: Sandbox::run_capture -> CmdOutcome (captura stdout completo + exit, tee de stderr) + run_evidence(recipe,cfg,store) -> EvidenceReport que reproduce el artefacto (cache-hit), levanta un sandbox con fuente+build-deps+el artefacto instalado como capa overlay (binarios en PATH) y corre cada check. (3) CLI: swm-verify --evidence reconstruye cada source_patch y corre run_evidence; imprime veredicto por check + estrato máximo alcanzado; exit != 0 si algún check falla. Es un runner de comandos con hash del output, no un framework — la confianza vive en el checker. Verificado e2e: tree con [[evidence.checks]] cmd-exit 'tree --version' → pack cache-hitea (evidencia no cambia el hash) → swm-verify --evidence corre el check en el sandbox: pasa (exit 0) y falla con expected_exit=7 (exit 1, 'NO proponer'). Núcleo puro con tests unitarios. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -134,6 +134,10 @@ enum Cmd {
|
||||
/// /var/lib/hammer/trust. La firma reporta estado, NO autoriza promover (ver SDD 09).
|
||||
#[arg(long)]
|
||||
trust: Option<PathBuf>,
|
||||
/// [H1] Además del schema/firma, REPRODUCE cada paquete source_patch y corre su bloque
|
||||
/// `evidence` (proof-carrying recipes) en el sandbox. Falla ⇒ exit != 0 (no proponer).
|
||||
#[arg(long)]
|
||||
evidence: bool,
|
||||
},
|
||||
/// [Fase 4] Genera un par de claves Ed25519 para firmar `.swm`. Escribe `<name>.ed25519`
|
||||
/// (privada, 0600) y `<name>.ed25519.pub` en `--out` (default: /var/lib/hammer/trust).
|
||||
@@ -798,8 +802,14 @@ fn main() -> anyhow::Result<()> {
|
||||
None, // `apply` aplica un .swm genérico: no conoce el nombre del paquete
|
||||
)?;
|
||||
}
|
||||
Cmd::SwmVerify { file, base_ref, trust } => {
|
||||
run_swm_verify(&file, base_ref.as_deref(), trust.as_deref())?;
|
||||
Cmd::SwmVerify { file, base_ref, trust, evidence } => {
|
||||
run_swm_verify(
|
||||
&file,
|
||||
base_ref.as_deref(),
|
||||
trust.as_deref(),
|
||||
evidence,
|
||||
std::path::Path::new(&cli.store),
|
||||
)?;
|
||||
}
|
||||
Cmd::Keygen { name, out } => {
|
||||
run_keygen(&name, out.as_deref())?;
|
||||
@@ -1208,6 +1218,8 @@ fn run_swm_verify(
|
||||
file: &str,
|
||||
base_ref: Option<&std::path::Path>,
|
||||
trust: Option<&std::path::Path>,
|
||||
evidence: bool,
|
||||
store_path: &std::path::Path,
|
||||
) -> anyhow::Result<()> {
|
||||
let swm = load_swm(file)?;
|
||||
swm.verify_schema()
|
||||
@@ -1228,9 +1240,78 @@ fn run_swm_verify(
|
||||
eprintln!("verify_base: omitido (sin --base-ref)");
|
||||
}
|
||||
print_sig_status(&swm, trust)?;
|
||||
if evidence {
|
||||
run_swm_evidence(&swm, store_path)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// [H1b] Reproduce cada paquete `source_patch` del `.swm` y corre su bloque `evidence` en el sandbox
|
||||
/// reproducible. Imprime el veredicto por check y ABORTA (exit != 0) si alguno falla — el gate de
|
||||
/// "reproduce + estas N pruebas pasan" antes de que el humano proponga (SDD 15 §H1). Frontera:
|
||||
/// paquetes con build-deps necesitan esas recetas resolubles en el catálogo del lab (como `install`);
|
||||
/// para el caso self-contained (binario estático sin deps) alcanza con el `.swm`.
|
||||
fn run_swm_evidence(swm: &hammer_core::Swm, store_path: &std::path::Path) -> anyhow::Result<()> {
|
||||
// Corto-circuito ANTES de tocar el store/catálogo: si ninguna mutación trae evidencia, no hay
|
||||
// nada que reproducir (y no forzamos abrir un store que quizá no exista/no sea escribible).
|
||||
let has_evidence = swm.mutations.iter().any(|m| {
|
||||
matches!(m, hammer_core::Mutation::SourcePatch { evidence, .. } if !evidence.is_empty())
|
||||
});
|
||||
if !has_evidence {
|
||||
println!("evidence: ninguna mutación declara evidencia (nada que verificar)");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let store = hammer_core::Store::open(store_path)?;
|
||||
let cfg = hammer_build::BuildConfig::from_env_or_defaults(store.root());
|
||||
let catalog_dir = hammer_build::swm_bridge::catalog_dir_for(&cfg, None);
|
||||
std::fs::create_dir_all(&catalog_dir)?;
|
||||
|
||||
let mut ran = 0usize;
|
||||
let mut failed = 0usize;
|
||||
for m in &swm.mutations {
|
||||
let hammer_core::Mutation::SourcePatch { evidence, target_bin, .. } = m else {
|
||||
continue;
|
||||
};
|
||||
if evidence.is_empty() {
|
||||
continue;
|
||||
}
|
||||
let recipe =
|
||||
hammer_build::swm_bridge::recipe_from_source_patch(m, None, &catalog_dir)?;
|
||||
println!(
|
||||
"evidence: reproduciendo {target_bin} y corriendo {} check(s)…",
|
||||
evidence.checks.len()
|
||||
);
|
||||
let report = hammer_build::run_evidence(&recipe, &cfg, &store)?;
|
||||
for r in &report.results {
|
||||
let mark = if r.outcome.passed { "✓" } else { "✗" };
|
||||
println!(
|
||||
" {mark} [{}] {} — {}",
|
||||
r.kind.as_str(),
|
||||
r.cmd,
|
||||
r.outcome.detail
|
||||
);
|
||||
}
|
||||
ran += report.results.len();
|
||||
failed += report.results.iter().filter(|r| !r.outcome.passed).count();
|
||||
if report.all_passed() {
|
||||
if let Some(lvl) = report.max_level_passed() {
|
||||
println!(" → verificado hasta el estrato '{}'", lvl.as_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ran == 0 {
|
||||
println!("evidence: ninguna mutación declara evidencia (nada que verificar)");
|
||||
Ok(())
|
||||
} else if failed > 0 {
|
||||
anyhow::bail!("evidence: {failed}/{ran} check(s) fallaron ⇒ NO proponer");
|
||||
} else {
|
||||
println!("evidence: {ran} check(s), todos pasan ✓");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
const DEFAULT_TRUST_DIR: &str = "/var/lib/hammer/trust";
|
||||
|
||||
/// Carga el TrustStore (de `--trust` o el default) y reporta el estado de la firma. La firma
|
||||
|
||||
Reference in New Issue
Block a user