cierre #2 del SDD 17: hammer why-differs — el diffoscope propio
Cuando un artefacto no reproduce, el store sólo sabe decir "el hash no coincide" y el resto es trabajo artesanal. Esto responde POR QUÉ, en términos de la CAUSA y no del byte: - gzip con MTIME embebido (bytes 4..8) → remedio: `gzip -n` - cabecera `ar` de un `.a` (mtime/uid/gid) → remedio: modo determinista (`ar D`) - secciones ELF, con lectura experta: sólo `.comment` ⇒ otra versión de compilador; sólo `.debug_*` ⇒ rutas de build; sólo `.symtab`/`.dynsym` ⇒ orden de símbolos (código idéntico); sólo build-id ⇒ residuo, no causa raíz. En un `.a` dice QUÉ MIEMBRO difiere. - ruta del árbol de build embebida, texto (línea que difiere), y bytes como último recurso. Y sobre todo trae la EVIDENCIA, no sólo la hipótesis: para las secciones de texto extrae las cadenas que están en un ELF y no en el otro. Caso real que lo motivó (alsa-lib): la interpretación decía "típicamente rutas de build" y la evidencia mostró `/src/target/release/build/libsodium-sys-<hash-cargo>/out/…`. Sin la cadena era una corazonada; reproducir eso a mano cuesta varios readelf, la herramienta lo da en 40ms. Descenso, no comparación total: sólo baja donde los hashes difieren (el cruce con format/ reconcile del SDD 17). Sin dependencias externas — parsers gzip/ar/ELF propios, como manda el ADR 0004: un diffoscope de verdad se apoya en medio mundo de binarios ajenos. `--json` para el bucle agéntico; exit 0 si reproduce, 1 si diverge (encadenable en scripts). `scripts/why-differs-barrido.sh` lo pasa por todo el store y separa los dos casos que se confunden a ojo: recipe.toml distinto (divergencia esperada) vs recipe.toml IDÉNTICO y artefacto distinto (no-reproducción a investigar). 5 tests nuevos; los 142 de hammer-core siguen en verde. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -49,6 +49,20 @@ enum Cmd {
|
||||
#[arg(long)]
|
||||
check: bool,
|
||||
},
|
||||
/// [SDD 17 §1.2] Explica POR QUÉ dos artefactos difieren — el diffoscope propio. Cuando un hash
|
||||
/// no reproduce, el store sólo sabe decir "no coincide"; esto nombra la CAUSA: un MTIME embebido
|
||||
/// en un gzip, la cabecera `ar` de un `.a`, qué sección ELF cambió (`.comment` ⇒ otro compilador),
|
||||
/// una ruta del árbol de build filtrada al binario. Desciende sólo donde los hashes difieren.
|
||||
/// Exit 0 si los dos árboles son idénticos, 1 si divergen.
|
||||
WhyDiffers {
|
||||
/// Artefacto A: un directorio del store (`store/<hash>-<nombre>`) o cualquier árbol.
|
||||
a: String,
|
||||
/// Artefacto B, el que se compara contra A.
|
||||
b: String,
|
||||
/// Informe JSON — evidencia legible por máquina para el barrido de granja y el bucle agéntico.
|
||||
#[arg(long)]
|
||||
json: bool,
|
||||
},
|
||||
/// [Atestación] Verifica un árbol FHS (p.ej. un product-rootfs hidratado) contra su
|
||||
/// `/ente/attest.json`: recomputa el BLAKE3 de cada binario crítico y lo compara. Es el gate de
|
||||
/// integridad que arje aplicará al boot, hecho hoy por hammer ("reproducir, no confiar"). Exit≠0
|
||||
@@ -785,6 +799,41 @@ fn main() -> anyhow::Result<()> {
|
||||
println!("{hash}");
|
||||
}
|
||||
}
|
||||
Cmd::WhyDiffers { a, b, json } => {
|
||||
let informe = hammer_core::why_differs(std::path::Path::new(&a), std::path::Path::new(&b))?;
|
||||
if json {
|
||||
println!("{}", serde_json::to_string_pretty(&informe)?);
|
||||
} else {
|
||||
println!("== why-differs");
|
||||
println!(" A: {}", informe.a);
|
||||
println!(" B: {}", informe.b);
|
||||
println!(
|
||||
" {} entradas idénticas · {} divergen",
|
||||
informe.identicos,
|
||||
informe.divergencias.len()
|
||||
);
|
||||
for d in &informe.divergencias {
|
||||
println!("\n {}", d.ruta);
|
||||
println!(" causa: {}", d.explicacion);
|
||||
if let Some(r) = &d.remedio {
|
||||
println!(" → {r}");
|
||||
}
|
||||
}
|
||||
if informe.reproduce() {
|
||||
println!("\n ✓ los dos árboles son idénticos: REPRODUCE");
|
||||
} else {
|
||||
let resumen: Vec<String> = informe
|
||||
.por_causa()
|
||||
.iter()
|
||||
.map(|(c, n)| format!("{c}×{n}"))
|
||||
.collect();
|
||||
println!("\n resumen: {}", resumen.join(" "));
|
||||
}
|
||||
}
|
||||
if !informe.reproduce() {
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
Cmd::Attest { rootfs } => {
|
||||
let report = hammer_bootstrap::verify_attestation(std::path::Path::new(&rootfs))?;
|
||||
for p in &report.ok {
|
||||
|
||||
Reference in New Issue
Block a user