205 lineas en 73 ficheros de crates, mas la prosa de CLAUDE.md y del skill, que se me habian quedado afuera de los barridos anteriores (no eran ni recetas ni docs/ ni scripts/). EL BARRIDO ANCHO ESTUVO A UN COMMIT DE ROMPER EL CORPUS ENTERO. El primer intento reescribia los .rs completos, no solo los comentarios. Entre las lineas de codigo que tocaba estaban SIETE etiquetas de separacion de dominio, que son ENTRADA DE HASH: b"hammer-tree-v1" <- el prefijo de ArtifactHash::of_tree (hash.rs:60) b"hammer-seed-v1" la funcion que hashea TODOS los artefactos: b"hammer-stage1-rootfs-v2" cambiarla mueve los 4750 hashes del store b"hammer-product-rootfs-v3" b"hammer-product-attested-v2" b"hammer-builder-rootfs-v1" b"hammer-attest-dev-rootkey-0001!!" <- clave raiz de atestacion, [u8;32] Revertido y rehecho solo sobre comentarios, esquivando ademas las cadenas crudas de Rust (r#"..."#) porque el SYSTEM_PROMPT del traductor tiene lineas que empiezan como comentario. Controles: las 7 etiquetas siguen ahi, el diff toca CERO lineas de codigo, 605 tests en verde y el hash de zlib sigue en b3:dc363f26. La leccion es la misma de toda esta etapa: un literal que parece prosa puede ser entrada de hash, y la unica forma de saberlo es mirar donde se usa.
77 lines
2.8 KiB
Rust
77 lines
2.8 KiB
Rust
//! E2E del CLI `takana bootstrap stage0` (track posterior, SDD 11). Construye un tarball de
|
|
//! semilla falso, lo ingiere vía `file://` (sin red) y verifica el camino completo:
|
|
//! sella + imprime el hash, es idempotente, y un sha256 erróneo falla sin sellar.
|
|
|
|
use std::path::Path;
|
|
use std::process::Command;
|
|
|
|
const BIN: &str = env!("CARGO_BIN_EXE_hammer");
|
|
|
|
fn sha256_hex(bytes: &[u8]) -> String {
|
|
use sha2::{Digest, Sha256};
|
|
let mut h = Sha256::new();
|
|
h.update(bytes);
|
|
hex::encode(h.finalize())
|
|
}
|
|
|
|
/// Crea un tarball con `bin/zig` dentro y devuelve `(url file://, sha256 hex)`.
|
|
fn make_seed_tarball(dir: &Path) -> (String, String) {
|
|
let src = dir.join("src");
|
|
std::fs::create_dir_all(src.join("bin")).unwrap();
|
|
std::fs::write(src.join("bin/zig"), b"#!/bin/sh\necho fake zig\n").unwrap();
|
|
let tarball = dir.join("seed.tar.gz");
|
|
let st = Command::new("tar")
|
|
.arg("-czf")
|
|
.arg(&tarball)
|
|
.arg("-C")
|
|
.arg(&src)
|
|
.arg(".")
|
|
.status()
|
|
.unwrap();
|
|
assert!(st.success());
|
|
let sha = sha256_hex(&std::fs::read(&tarball).unwrap());
|
|
(format!("file://{}", tarball.display()), sha)
|
|
}
|
|
|
|
fn stage0(store: &Path, url: &str, sha256: &str, version: &str) -> std::process::Output {
|
|
Command::new(BIN)
|
|
.arg("--store")
|
|
.arg(store)
|
|
.args(["bootstrap", "stage0", "--url"])
|
|
.arg(url)
|
|
.args(["--sha256", sha256, "--version", version])
|
|
.output()
|
|
.unwrap()
|
|
}
|
|
|
|
#[test]
|
|
fn stage0_seals_idempotent_and_rejects_bad_sha() {
|
|
let tmp = tempfile::tempdir().unwrap();
|
|
let (url, sha) = make_seed_tarball(tmp.path());
|
|
let store = tmp.path().join("store");
|
|
|
|
// --- primera ingestión: sella e imprime el hash ---
|
|
let out = stage0(&store, &url, &sha, "0.13.0");
|
|
assert!(out.status.success(), "stderr: {}", String::from_utf8_lossy(&out.stderr));
|
|
let hash = String::from_utf8(out.stdout).unwrap().trim().to_string();
|
|
assert!(hash.starts_with("b3:"), "hash inesperado: {hash}");
|
|
|
|
// El artefacto está en el store con el contenido extraído.
|
|
let zig = store.join(format!("{}-seed-zig", hash.trim_start_matches("b3:"))).join("bin/zig");
|
|
assert!(zig.is_file(), "bin/zig debe existir en el artefacto sellado");
|
|
|
|
// --- idempotente: mismo hash, sin re-sellar ---
|
|
let out2 = stage0(&store, &url, &sha, "0.13.0");
|
|
assert!(out2.status.success());
|
|
assert_eq!(String::from_utf8(out2.stdout).unwrap().trim(), hash);
|
|
|
|
// --- sha erróneo: falla y no sella un artefacto nuevo ---
|
|
let bad = stage0(&store, &url, &"0".repeat(64), "bad");
|
|
assert!(!bad.status.success(), "un sha erróneo debe fallar");
|
|
assert!(
|
|
String::from_utf8_lossy(&bad.stderr).contains("sha256 mismatch"),
|
|
"stderr: {}",
|
|
String::from_utf8_lossy(&bad.stderr)
|
|
);
|
|
}
|