sergio/takana
> Una distribución Linux construida con un laboratorio funcional y hermético en el > sótano, y una terminal mutable, clásica y anárquica en el piso de arriba — diseñada > desde el suelo para que una IA programadora entienda, modifique y co…
takana etapa 5d: comentarios de crates, CLAUDE.md y el skill de granja
1//! Guardián de SDD 25 §7-H4: **ningún camino de spawn de takana usa `pre_exec`**.
2//!
3//! POR QUÉ. `std::process::Command` de Rust usa `posix_spawn` cuando puede, y `posix_spawn` es
4//! PLANO respecto al tamaño del padre: a 256 MB tocados, `fork+exec` costó 3 949 µs contra 231 µs
5//! (SDD 25 T1, **17×**). En cuanto se le pone un `pre_exec`, Rust apaga ese camino y cae a
6//! `fork+exec`, que copia el espacio de direcciones del padre. takana lanza builds desde un
7//! proceso que puede tener cientos de MB mapeados.
8//!
9//! Hoy `takana-build::sandbox` lanza `bwrap` sin `pre_exec` — y hasta ahora eso era SUERTE, no una
10//! regla: nadie lo comprobaba. Esto lo vuelve una regla.
11//!
12//! CUÁNDO SE PUEDE APAGAR. Si algún día hace falta de verdad (un `setsid`, un `unshare` que bwrap
13//! no cubra), la respuesta no es borrar el test: es escribir acá por qué ese caso paga los 17× y
14//! ponerlo en la lista de excepciones, para que el precio quede a la vista de quien lo lea.
15
16use std::path::{Path, PathBuf};
17
18fn raiz_del_workspace() -> PathBuf {
19 // CARGO_MANIFEST_DIR = crates/hammer-build
20 PathBuf::from(env!("CARGO_MANIFEST_DIR"))
21 .parent()
22 .and_then(Path::parent)
23 .expect("crates/<crate> tiene dos ancestros")
24 .to_path_buf()
25}
26
27fn rs_de(dir: &Path, out: &mut Vec<PathBuf>) {
28 let Ok(rd) = std::fs::read_dir(dir) else { return };
29 for e in rd.flatten() {
30 let p = e.path();
31 if p.is_dir() {
32 rs_de(&p, out);
33 } else if p.extension().is_some_and(|x| x == "rs") {
34 out.push(p);
35 }
36 }
37}
38
39#[test]
40fn ningun_spawn_de_hammer_usa_pre_exec() {
41 let raiz = raiz_del_workspace();
42 let crates = raiz.join("crates");
43 assert!(crates.is_dir(), "no encontré {} — ¿se movió el workspace?", crates.display());
44
45 let mut fuentes = Vec::new();
46 rs_de(&crates, &mut fuentes);
47 // Un barrido que no encuentra fuentes aprueba por vacío, que es el fallo de CLAUDE.md §3.
48 assert!(fuentes.len() > 20, "sólo {} fuentes: el barrido no miró nada", fuentes.len());
49
50 let mut culpables = Vec::new();
51 for f in &fuentes {
52 if f == Path::new(file!()) || f.ends_with("tests/sin_pre_exec.rs") {
53 continue;
54 }
55 let Ok(txt) = std::fs::read_to_string(f) else { continue };
56 for (i, l) in txt.lines().enumerate() {
57 let limpia = l.trim_start();
58 if limpia.starts_with("//") || limpia.starts_with("*") {
59 continue; // documentarlo no es usarlo
60 }
61 if l.contains("pre_exec") {
62 culpables.push(format!("{}:{}: {}", f.display(), i + 1, l.trim()));
63 }
64 }
65 }
66
67 assert!(
68 culpables.is_empty(),
69 "SDD 25 §7-H4: `pre_exec` apaga el camino `posix_spawn` de Rust y devuelve el spawn a \
70 `fork+exec` (17× más caro con 256 MB en el padre, SDD 25 T1). Aparece en:\n {}",
71 culpables.join("\n ")
72 );
73}