- 1
#![allow(clippy::expect_used)] - 2
- 3
use std::path::PathBuf; - 4
use std::sync::Arc; - 5
- 6
use serde_json::json; - 7
use tempfile::tempdir; - 8
use vak_tools::sandbox::{Sandbox, SandboxMode, SandboxTarget, Seatbelt}; - 9
use vak_tools::{ToolContext, brokered_default_tools}; - 10
- 11
fn worker() -> PathBuf { - 12
PathBuf::from(env!("CARGO_BIN_EXE_vak")) - 13
} - 14
- 15
fn tool(name: &str) -> Arc<dyn vak_tools::Tool> { - 16
brokered_default_tools(worker()) - 17
.into_iter() - 18
.find(|tool| tool.name() == name) - 19
.expect("built-in tool") - 20
} - 21
- 22
struct CommandSandbox; - 23
- 24
impl Sandbox for CommandSandbox { - 25
fn name(&self) -> &str { - 26
"test-command" - 27
} - 28
- 29
fn wrap(&self, command: &str) -> String { - 30
format!("printf 'command-boundary\\n'; {command}") - 31
} - 32
- 33
fn target(&self) -> SandboxTarget { - 34
SandboxTarget::ToolCommand - 35
} - 36
} - 37
- 38
#[tokio::test] - 39
async fn builtins_execute_in_worker_process() { - 40
let workspace = tempdir().expect("workspace"); - 41
let ctx = ToolContext::new(workspace.path().to_path_buf()); - 42
- 43
let write = tool("write") - 44
.execute(&json!({"path": "inside.txt", "content": "brokered"}), &ctx) - 45
.await; - 46
assert!(!write.is_error, "{}", write.content); - 47
- 48
let read = tool("read") - 49
.execute(&json!({"path": "inside.txt"}), &ctx) - 50
.await; - 51
assert!(!read.is_error, "{}", read.content); - 52
assert!(read.content.contains("brokered")); - 53
} - 54
- 55
#[tokio::test] - 56
async fn brokered_bash_streams_live_sandbox_events_with_identity() { - 57
let workspace = tempdir().expect("workspace"); - 58
let (sink, mut events) = - 59
vak_tools::sandbox_events::SandboxEventSink::new_with_id("broker-stress".into()); - 60
let ctx = ToolContext::new(workspace.path().to_path_buf()).with_sandbox_sink(sink); - 61
let output = tool("bash") - 62
.execute( - 63
&json!({"command": "mkdir -p nested && echo brokered > nested/result.txt"}), - 64
&ctx, - 65
) - 66
.await; - 67
assert!(!output.is_error, "{}", output.content); - 68
let mut started = false; - 69
let mut artifact = false; - 70
while let Ok(event) = events.try_recv() { - 71
match event { - 72
vak_tools::SandboxEvent::ExecutionStarted { execution_id, .. } => { - 73
assert_eq!(execution_id, "broker-stress"); - 74
started = true; - 75
} - 76
vak_tools::SandboxEvent::ArtifactGenerated { - 77
execution_id, path, .. - 78
} => { - 79
assert_eq!(execution_id, "broker-stress"); - 80
artifact |= path.contains("nested/result.txt"); - 81
} - 82
_ => {} - 83
} - 84
} - 85
assert!(started, "broker did not stream a start event"); - 86
assert!(artifact, "broker did not stream the nested artifact event"); - 87
} - 88
- 89
#[tokio::test] - 90
async fn missing_worker_fails_closed() { - 91
let workspace = tempdir().expect("workspace"); - 92
let mut tools = vak_tools::brokered_default_tools(workspace.path().join("missing-worker")); - 93
let output = tools - 94
.remove(0) - 95
.execute( - 96
&json!({"path": "anything"}), - 97
&ToolContext::new(workspace.path().to_path_buf()), - 98
) - 99
.await; - 100
assert!(output.is_error); - 101
assert!(output.content.contains("broker unavailable")); - 102
} - 103
- 104
#[tokio::test] - 105
async fn cancellation_terminates_worker_process_group() { - 106
let workspace = tempdir().expect("workspace"); - 107
let ctx = ToolContext::new(workspace.path().to_path_buf()); - 108
let cancel = ctx.cancel.clone(); - 109
tokio::spawn(async move { - 110
tokio::time::sleep(std::time::Duration::from_millis(1000)).await; - 111
cancel.cancel(); - 112
}); - 113
- 114
let output = tokio::time::timeout( - 115
std::time::Duration::from_secs(5), - 116
tool("bash").execute(&json!({"command": "sleep 30"}), &ctx), - 117
) - 118
.await - 119
.expect("broker cancellation deadline"); - 120
assert!(output.is_error); - 121
assert!(output.content.contains("cancelled")); - 122
} - 123
- 124
#[tokio::test] - 125
async fn command_scoped_sandbox_wraps_bash_inside_protocol() { - 126
let workspace = tempdir().expect("workspace"); - 127
let mut ctx = ToolContext::new(workspace.path().to_path_buf()); - 128
ctx.sandbox = Some(Arc::new(CommandSandbox)); - 129
- 130
let output = tool("bash") - 131
.execute(&json!({"command": "printf 'tool-command\\n'"}), &ctx) - 132
.await; - 133
assert!(!output.is_error, "{}", output.content); - 134
assert!(output.content.contains("command-boundary")); - 135
assert!(output.content.contains("tool-command")); - 136
} - 137
- 138
#[cfg(target_os = "macos")] - 139
#[tokio::test] - 140
async fn restricted_worker_cannot_read_home_outside_workspace() { - 141
let workspace = tempdir().expect("workspace"); - 142
let home = PathBuf::from(std::env::var_os("HOME").expect("home")); - 143
let protected = tempfile::Builder::new() - 144
.prefix("vak-broker-protected-") - 145
.tempdir_in(home) - 146
.expect("protected directory"); - 147
let secret = protected.path().join("secret.txt"); - 148
std::fs::write(&secret, "must-not-cross-boundary").expect("secret fixture"); - 149
let mut ctx = ToolContext::new(workspace.path().to_path_buf()); - 150
let mut seatbelt = Seatbelt::new(SandboxMode::WorkspaceWrite, workspace.path()); - 151
seatbelt - 152
.read_paths - 153
.push(worker().parent().expect("worker parent").to_path_buf()); - 154
ctx.sandbox = Some(Arc::new(seatbelt)); - 155
- 156
let output = tool("read").execute(&json!({"path": secret}), &ctx).await; - 157
assert!(output.is_error); - 158
assert!(!output.content.contains("must-not-cross-boundary")); - 159
assert!( - 160
!output.content.contains("tool broker exited"), - 161
"{}", - 162
output.content - 163
); - 164
} - 165
Indexing the workspace…
Vakyartha documentation is discovering safe artifacts, anchors, and source references.