gioser

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…

TOML 1471Shell 173Rust 105Markdown 943545 Definitions

⎇ 9fe1e010e21199c12896ff156f85884a83f04ce6 crates/takana-agent/tests/orchestrator_evidence_gate.rs Rust · 161 lines · 5.5 KB History Who touched what raw

paquetería: zsh se puede instalar — los parches múltiples viajaban unidos y el target_bin era una adivinanza 9fe1e010e · Sergio ·

1//! [H1c] Gate de evidencia en el orquestador contra un bus stub.
2//!
3//! El lab (aquí, un stub) ejecuta la evidencia tras sellar el artefacto y devuelve el veredicto
4//! en `BuildReady`. Si algún check falla, `Orchestrator::run` debe ABORTAR con "no se propone" y
5//! NO hidratar nada al `prefix` — el gate "falla ⇒ no se propone" del SDD 15 §H1.
6
7use std::io::{BufRead, BufReader, Write};
8use std::os::unix::net::{UnixListener, UnixStream};
9use std::path::PathBuf;
10use std::thread;
11use std::time::Duration;
12
13use takana_agent::orchestrator::{ApplyTarget, CompileMode, Orchestrator};
14use takana_agent::MockTranslator;
15use takana_core::proto::{
16    Cap, Command, Event, EvidenceCheckVerdict, EvidenceVerdict, Peer, PROTOCOL_VERSION,
17};
18use takana_core::swm::{Base, Mutation, Swm, SwmBuild};
19use takana_core::{BaseRef, Evidence, EvidenceCheck, EvidenceKind};
20use std::collections::BTreeMap;
21
22/// Bus stub que responde a `Compile` con `BuildReady` cuyo veredicto tiene `all_passed=false`.
23fn start_failing_bus(sock: PathBuf) {
24    let _ = std::fs::remove_file(&sock);
25    let listener = UnixListener::bind(&sock).expect("bind stub");
26    thread::spawn(move || {
27        if let Some(Ok(stream)) = listener.incoming().next() {
28            thread::spawn(move || serve(stream));
29        }
30    });
31}
32
33fn serve(stream: UnixStream) {
34    let mut writer = stream.try_clone().unwrap();
35    let mut reader = BufReader::new(stream);
36    let send = |w: &mut UnixStream, ev: &Event| {
37        w.write_all(serde_json::to_string(ev).unwrap().as_bytes()).unwrap();
38        w.write_all(b"\n").unwrap();
39        w.flush().unwrap();
40    };
41
42    let mut first = String::new();
43    reader.read_line(&mut first).unwrap();
44    let Ok(Command::Hello { .. }) = serde_json::from_str::<Command>(first.trim()) else {
45        return;
46    };
47    send(
48        &mut writer,
49        &Event::Welcome {
50            ver: PROTOCOL_VERSION,
51            caps: vec![Cap::Query, Cap::Compile, Cap::Inject, Cap::Init],
52            peer: Peer { uid: 1000, gid: 1000, pid: 1 },
53        },
54    );
55
56    loop {
57        let mut line = String::new();
58        match reader.read_line(&mut line) {
59            Ok(0) | Err(_) => break,
60            Ok(_) => {}
61        }
62        if line.trim().is_empty() {
63            continue;
64        }
65        let Ok(cmd) = serde_json::from_str::<Command>(line.trim()) else {
66            continue;
67        };
68        if let Command::Compile { recipe } = cmd {
69            send(
70                &mut writer,
71                &Event::BuildReady {
72                    recipe: recipe.name.clone(),
73                    artifact: "b3:stub-fail".into(),
74                    verdict: Some(EvidenceVerdict {
75                        all_passed: false,
76                        max_level: None,
77                        checks: vec![EvidenceCheckVerdict {
78                            kind: EvidenceKind::CmdExit,
79                            cmd: "grep --version".into(),
80                            passed: false,
81                            detail: "exit 7 ≠ esperado 0".into(),
82                        }],
83                    }),
84                },
85            );
86        }
87    }
88}
89
90fn swm_with_evidence() -> Swm {
91    Swm {
92        swm_version: 1,
93        base: Base { distro_version: "2026-06-06".into(), pins: BTreeMap::new() },
94        mutations: vec![Mutation::SourcePatch {
95            repo: Some("git://x/grep.git".into()),
96            commit: Some("abc".into()),
97            tarball: None,
98            sha256: None,
99            strip_components: None,
100            patch: None,
101            patches: Vec::new(),
102            patch_url: None,
103            build: SwmBuild {
104                compiler: "zig-cc".into(),
105                target: "x86_64-linux-musl".into(),
106                link: "static".into(),
107                flags: vec![],
108                phases: Default::default(),
109                zig_version: None,
110                strip_debug: None,
111            },
112            target_bin: "/usr/bin/grep".into(),
113            expected_hash: None,
114            deps: Default::default(),
115            evidence: Evidence {
116                checks: vec![EvidenceCheck {
117                    kind: EvidenceKind::CmdExit,
118                    cmd: "grep --version".into(),
119                    expected_exit: 0,
120                    expected_output: None,
121                }],
122            },
123            slots: Default::default(),
124        }],
125        signature: None,
126    }
127}
128
129#[test]
130fn failing_evidence_aborts_and_does_not_hydrate() {
131    let d = tempfile::tempdir().unwrap();
132    let sock = d.path().join("agent.sock");
133    start_failing_bus(sock.clone());
134    for _ in 0..50 {
135        if sock.exists() {
136            break;
137        }
138        thread::sleep(Duration::from_millis(10));
139    }
140
141    let prefix = d.path().join("prefix");
142    let store = d.path().join("store");
143    let base = BaseRef { distro_version: "2026-06-06".into(), pins: BTreeMap::new() };
144    let t = MockTranslator::empty().with_intent("compile grep", swm_with_evidence());
145    let o = Orchestrator::new(
146        t,
147        base,
148        ApplyTarget::Prefix { prefix: prefix.clone() },
149        CompileMode::ViaBus { sock, store, timeout: Duration::from_secs(5) },
150    );
151
152    let err = o.run("compile grep").unwrap_err().to_string();
153    assert!(err.contains("no se propone"), "mensaje inesperado: {err}");
154    assert!(err.contains("evidencia") || err.contains("check(s)"), "{err}");
155
156    // El gate corta ANTES de hidratar: el target_bin no debe existir bajo el prefix.
157    assert!(
158        !prefix.join("usr/bin/grep").exists(),
159        "el binario se hidrató pese al fallo de evidencia"
160    );
161}