hammer hash: dry-run que faltaba — cierra el sobre-reporte del static-audit de raíz
El agujero que dejé señalado 3 veces esta sesión: nada podía saber el sellado VIGENTE de una receta sin construirla, así que static-audit.sh auditaba el más reciente por mtime (ls -dt) y acusaba a recetas ya sanas (dbus/libnl) por un sellado anterior a sus flags. FIX = subcomando `hammer hash <receta> [--check]`. Calcula el ArtifactHash puro sobre las recetas (source_id + compiler/target/link + patches + flags + fases + hashes de deps recursivos) SIN bajar fuentes ni compilar. artifact_hash() ya era pub; el CLI sólo lo expone. Cero cambios en la lógica de hashing ⇒ NINGÚN sellado se mueve. Verificado: `hash` da EXACTAMENTE el mismo hash que `build` (samurai, byte a byte); `--check` sobre receta editada → NO-SELLADO en 2ms, exit 1, sin construir nada. static-audit.sh ahora selecciona el artefacto VIGENTE por hash, no el más reciente por mtime. Con fallback a ls -dt si hammer no está compilado. Efecto en el store completo: las ~65 recetas cuyo sellado no es el vigente pasan de 'auditadas' (falsa cobertura) a 'sin artefacto' (deuda de rebuild REAL, ahora visible): estáticas de verdad 615 | MIENTEN 0 | sin artefacto 123. Corre en 15s, sin build. El '58 sin medir' de antes estaba enmascarando ~65 recetas más. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -37,6 +37,18 @@ enum Cmd {
|
||||
/// Ruta a la receta TOML.
|
||||
recipe: String,
|
||||
},
|
||||
/// [Fase 0] Calcula el `ArtifactHash` de una receta SIN construir — puro sobre las recetas
|
||||
/// (source_id + compiler/target/link + patches + flags + fases + hashes de deps). No baja
|
||||
/// fuentes ni compila. Responde "¿cuál es el hash VIGENTE de esta receta?", que el store no
|
||||
/// puede dar sin sellar. Con `--check`: exit 0 si el artefacto vigente ya está en el store,
|
||||
/// 1 si no — el dry-run que le faltaba a scripts/static-audit.sh para no auditar sellados rancios.
|
||||
Hash {
|
||||
/// Ruta a la receta TOML.
|
||||
recipe: String,
|
||||
/// No imprime el hash; verifica si el artefacto vigente está sellado en el store (exit 0/1).
|
||||
#[arg(long)]
|
||||
check: 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
|
||||
@@ -755,6 +767,24 @@ fn main() -> anyhow::Result<()> {
|
||||
let hash = hammer_build::build(&recipe, &cfg, &store)?;
|
||||
println!("{hash}");
|
||||
}
|
||||
Cmd::Hash { recipe, check } => {
|
||||
let store = hammer_core::Store::open(&cli.store)?;
|
||||
let recipe = hammer_core::Recipe::load_from_path(&recipe)?;
|
||||
let hash = hammer_build::artifact_hash(&recipe, &store)?;
|
||||
if check {
|
||||
// ¿el artefacto VIGENTE (el que corresponde a la receta de HOY) ya está sellado?
|
||||
// Es lo que static-audit.sh no podía saber: `ls -dt store/*-<n>` da el más RECIENTE,
|
||||
// no el vigente. Exit 0 = sellado al día; 1 = haría falta construir.
|
||||
if store.has(&hash, &recipe.name) {
|
||||
println!("{hash}\tSELLADO");
|
||||
} else {
|
||||
println!("{hash}\tNO-SELLADO");
|
||||
std::process::exit(1);
|
||||
}
|
||||
} else {
|
||||
println!("{hash}");
|
||||
}
|
||||
}
|
||||
Cmd::Attest { rootfs } => {
|
||||
let report = hammer_bootstrap::verify_attestation(std::path::Path::new(&rootfs))?;
|
||||
for p in &report.ok {
|
||||
|
||||
+21
-10
@@ -37,6 +37,7 @@ set -eu
|
||||
HUB="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
cd "$HUB"
|
||||
STORE="${STORE:-$PWD/store}"
|
||||
HAMMER="${HAMMER:-$HUB/target/release/hammer}"
|
||||
|
||||
if [ $# -gt 0 ]; then
|
||||
recetas=""
|
||||
@@ -49,17 +50,27 @@ mienten=0; honestos=0; sin=0
|
||||
for f in $recetas; do
|
||||
n=$(basename "$f" .toml)
|
||||
grep -qE '^[[:space:]]*link[[:space:]]*=[[:space:]]*"static"' "$f" || continue
|
||||
# `ls -dt`: el artefacto MÁS RECIENTE, no el alfabéticamente primero. El store guarda TODOS los
|
||||
# sellados de una receta (expat tenía 5, de junio a hoy) y el glob ordena por hash, que es
|
||||
# ruido: auditaba un artefacto de hace tres semanas y reportaba dinámica una receta ya
|
||||
# arreglada. Mismo bug de clase que comparar `libpng.a` con `libpng16.a` por `find | head -1`.
|
||||
# EL ARTEFACTO VIGENTE, por hash — NO el más reciente por mtime. `hammer hash` calcula el
|
||||
# ArtifactHash de la receta de HOY sin construir (~2ms, puro sobre las recetas). El store guarda
|
||||
# TODOS los sellados históricos de una receta (expat tenía 5); auditar el más reciente por mtime
|
||||
# sobre-reportaba (acusaba a dbus/libnl, ya sanas, por un sellado anterior a sus flags). Ahora
|
||||
# auditamos EXACTAMENTE el sellado que corresponde a la receta actual, o ninguno.
|
||||
#
|
||||
# HONESTIDAD: "más reciente" ≠ "vigente". Lo vigente sería el artefacto cuyo hash corresponde a
|
||||
# la receta de HOY, y hammer no expone ese hash sin construir (no hay `hammer hash`/dry-run).
|
||||
# Con el store al día ambos coinciden; si acabás de construir una receta vieja, no. Por eso el
|
||||
# audit se corre DESPUÉS del rebuild, nunca antes.
|
||||
art=$(ls -dt "$STORE"/*-"$n" 2>/dev/null | head -1) || true
|
||||
[ -z "${art:-}" ] && { sin=$((sin + 1)); continue; }
|
||||
# Fallback a `ls -dt` si `hammer` no está compilado: el audit sigue corriendo, sólo vuelve a
|
||||
# poder sobre-reportar (como antes de que existiera `hammer hash`).
|
||||
if [ -x "$HAMMER" ]; then
|
||||
h=$("$HAMMER" --store "$STORE" hash "$f" 2>/dev/null) || h=""
|
||||
if [ -n "$h" ] && [ -d "$STORE/${h#b3:}-$n" ]; then
|
||||
art="$STORE/${h#b3:}-$n"
|
||||
else
|
||||
# No hay sellado para la receta VIGENTE ⇒ no hay nada honesto que auditar (rancio no
|
||||
# cuenta). Es el "sin artefacto" legítimo: la receta cambió y falta rebuild.
|
||||
sin=$((sin + 1)); continue
|
||||
fi
|
||||
else
|
||||
art=$(ls -dt "$STORE"/*-"$n" 2>/dev/null | head -1) || true
|
||||
[ -z "${art:-}" ] && { sin=$((sin + 1)); continue; }
|
||||
fi
|
||||
|
||||
# El primer ELF *ejecutable* del artefacto. Una `.so` tiene NEEDED por definición y no prueba
|
||||
# nada; sólo el ejecutable dice si el enlace final fue estático.
|
||||
|
||||
Reference in New Issue
Block a user