Lot 6 : clients Rust du chiffrement et de l'exécution des outils

Sceller / ouvrir un blob par le Go (mémoire verrouillée : erreur dédiée,
rien d'écrit en clair) et faire exécuter un appel d'outil par sa boucle
— les deux ponts dont la conversation Rust aura besoin.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
MichaelandClaude Opus 5.5 committed 2026-10-15 08:49:56 +02:00
1 parent c38b1ac43e
commit 461ef8030d
3 files changed
+121

No files matched your search

+28
View File
@@ -30,6 +30,10 @@ pub struct FakeGo {
pub side_live: Mutex<bool>,
pub refuse: Mutex<Option<serde_json::Value>>,
pub oai_log: Mutex<Vec<String>>,
/// Mémoire verrouillée : seal/open répondent 423.
pub locked: Mutex<bool>,
/// Adresse d'écoute du faux Go (pour un client `Store` direct).
pub backend: Mutex<Option<SocketAddr>>,
}
impl FakeGo {
@@ -101,6 +105,29 @@ async fn handle(st: Arc<FakeGo>, req: Request<Incoming>) -> Response<Full<Bytes>
let key = st.get("state", "web_key").unwrap_or_default();
resp(200, format!(r#"{{"web_key":"{key}","version":"0.15.0"}}"#))
}
"/internal/v1/crypt/seal" | "/internal/v1/crypt/open" => {
if *st.locked.lock().unwrap() {
return resp(423, r#"{"error":"mémoire verrouillée"}"#);
}
let body = req.into_body().collect().await.unwrap().to_bytes();
if path.ends_with("seal") {
let mut out = b"SCELLE:".to_vec();
out.extend_from_slice(&body);
resp(200, out)
} else {
resp(200, body.strip_prefix(b"SCELLE:").unwrap_or(&body).to_vec())
}
}
"/internal/v1/tool/exec" => {
let body = req.into_body().collect().await.unwrap().to_bytes();
let v: serde_json::Value = serde_json::from_slice(&body).unwrap();
let out = serde_json::json!({
"label": v["args"]["command"],
"result": format!("exec:{}", v["name"].as_str().unwrap()),
"caps_agent": v["caps"]["Agent"],
});
resp(200, out.to_string())
}
"/internal/v1/oai/route" => {
let key = st.get("state", "api_key").unwrap_or_default();
let v = serde_json::json!({
@@ -188,6 +215,7 @@ pub async fn start(home: std::path::PathBuf) -> (SocketAddr, Arc<FakeGo>, onesho
let st = Arc::new(FakeGo::default());
let l = TcpListener::bind("127.0.0.1:0").await.unwrap();
let backend = l.local_addr().unwrap();
*st.backend.lock().unwrap() = Some(backend);
let st2 = st.clone();
tokio::spawn(async move {
while let Ok((s, _)) = l.accept().await {
+45
View File
@@ -0,0 +1,45 @@
//! Lot 6 : clients Rust des API internes du chat (chiffrement, outils).
mod common;
use common::fakego;
use loki_rs::store::{CryptError, Store};
async fn store() -> (
Store,
std::sync::Arc<fakego::FakeGo>,
tokio::sync::oneshot::Sender<()>,
tempfile::TempDir,
) {
let t = tempfile::tempdir().unwrap();
let (_f, go, stop) = fakego::start(t.path().into()).await;
let backend = go.backend.lock().unwrap().unwrap();
(Store::new(backend, fakego::JETON.into()), go, stop, t)
}
#[tokio::test]
async fn sceller_ouvrir_et_verrou() {
let (st, go, _stop, _t) = store().await;
let sealed = st.seal(b"bonjour".to_vec()).await.unwrap();
assert_eq!(sealed, b"SCELLE:bonjour");
assert_eq!(st.open(sealed).await.unwrap(), b"bonjour");
*go.locked.lock().unwrap() = true;
assert_eq!(st.seal(b"x".to_vec()).await, Err(CryptError::Locked));
assert_eq!(st.open(b"x".to_vec()).await, Err(CryptError::Locked));
}
#[tokio::test]
async fn outil_execute_par_le_go() {
let (st, _go, _stop, _t) = store().await;
let v = st
.tool_exec(
"bash",
&serde_json::json!({ "command": "ls" }),
&serde_json::json!({ "Agent": true }),
)
.await
.unwrap();
assert_eq!(v["result"], "exec:bash");
assert_eq!(v["label"], "ls");
assert_eq!(v["caps_agent"], true);
}