use crate::host::compute::{
ComputeCapabilities, ComputeKind, Containment, ContainmentLevel, Durability, NetworkPolicy,
};
const MAX_NAMED_HOSTS: usize = 5;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct EnvironmentFacts {
pub kind: Option<ComputeKind>,
pub capabilities: ComputeCapabilities,
pub containment: Containment,
pub durability: Durability,
}
pub fn environment_preamble(facts: &EnvironmentFacts) -> Option<String> {
let kind = facts.kind?;
let mut lines = vec![format!("## Environment\n\n{}", where_commands_run(kind))];
if let Some(sentence) = what_it_can_do(&facts.capabilities) {
lines.push(sentence);
}
lines.push(containment_sentence(&facts.containment));
lines.push(network_sentence(&facts.containment.network));
lines.push(durability_sentence(facts.durability));
Some(lines.join("\n\n"))
}
fn where_commands_run(kind: ComputeKind) -> &'static str {
match kind {
ComputeKind::Host => {
"Commands run directly on the machine hosting this session. It is a real machine you \
share with whatever else runs on it."
}
ComputeKind::Machine => {
"Commands run on a registered remote machine reached over the network. It is a real \
machine that outlives this session."
}
ComputeKind::Vfs => {
"Commands run in an interpreter over this session's own filesystem. There is no \
operating system underneath: only what the interpreter implements exists."
}
ComputeKind::Container => {
"Commands run in a container created for this session, with its own filesystem and \
process table."
}
ComputeKind::Managed => {
"Commands run in a provider-managed remote sandbox created for this session, with its \
own filesystem and process table."
}
}
}
fn what_it_can_do(capabilities: &ComputeCapabilities) -> Option<String> {
let mut can: Vec<&str> = Vec::new();
let mut cannot: Vec<&str> = Vec::new();
for (supported, yes, no) in [
(
capabilities.native_processes,
"run native binaries and child processes",
"run native binaries — only what the interpreter implements is available",
),
(
capabilities.packages,
"install packages that persist across commands",
"install packages",
),
(
capabilities.pty,
"attach an interactive terminal",
"attach an interactive terminal, so commands must not expect one",
),
(
capabilities.ports,
"listen on ports and reach them",
"listen on ports that anything can reach",
),
] {
if supported {
can.push(yes);
} else {
cannot.push(no);
}
}
let mut parts = Vec::new();
if !can.is_empty() {
parts.push(format!("You can {}.", join_clauses(&can)));
}
if !cannot.is_empty() {
parts.push(format!("You cannot {}.", join_clauses(&cannot)));
}
(!parts.is_empty()).then(|| parts.join(" "))
}
fn containment_sentence(containment: &Containment) -> String {
let mut sentence = match containment.level {
ContainmentLevel::None => {
"Nothing isolates these commands from the host, so treat destructive operations as \
destructive to a real machine."
.to_string()
}
ContainmentLevel::Native => {
"A kernel policy bounds what these commands may touch.".to_string()
}
ContainmentLevel::Isolated => {
"A separate kernel, VM, or interpreter bounds these commands; the host is not \
reachable from here."
.to_string()
}
};
if !containment.writable_roots.is_empty() {
sentence.push_str(&format!(
" Writes are confined to {}.",
join_clauses(
&containment
.writable_roots
.iter()
.map(String::as_str)
.collect::<Vec<_>>()
)
));
}
sentence
}
fn network_sentence(network: &NetworkPolicy) -> String {
match network {
NetworkPolicy::Deny => {
"Outbound network access is denied. Anything needed from the network must already be \
present."
.to_string()
}
NetworkPolicy::Allow => "Outbound network access is open.".to_string(),
NetworkPolicy::Allowlist(hosts) if hosts.is_empty() => {
"Outbound network access is restricted to an allowlist, and nothing is on it."
.to_string()
}
NetworkPolicy::Allowlist(hosts) if hosts.len() <= MAX_NAMED_HOSTS => format!(
"Outbound network access is restricted to {}.",
join_clauses(&hosts.iter().map(String::as_str).collect::<Vec<_>>())
),
NetworkPolicy::Allowlist(hosts) => format!(
"Outbound network access is restricted to an allowlist of {} hosts; a request to \
anything else will fail.",
hosts.len()
),
}
}
fn durability_sentence(durability: Durability) -> String {
match durability {
Durability::Checkpointed => {
"This filesystem is checkpointed, so work here survives losing the compute.".to_string()
}
Durability::ProviderSnapshot => {
"The provider can snapshot this filesystem, which makes a restart fast but is not a \
guarantee. Anything that must survive belongs in session storage."
.to_string()
}
Durability::None => {
"Nothing recovers this filesystem if the compute is lost. Anything that must survive \
belongs in session storage."
.to_string()
}
}
}
fn join_clauses(items: &[&str]) -> String {
match items {
[] => String::new(),
[one] => (*one).to_string(),
[first, second] => format!("{first} and {second}"),
[rest @ .., last] => format!("{}, and {last}", rest.join(", ")),
}
}
#[cfg(test)]
mod tests {
use super::*;
fn facts(kind: ComputeKind) -> EnvironmentFacts {
EnvironmentFacts {
kind: Some(kind),
capabilities: ComputeCapabilities::full_machine(),
containment: Containment::isolated().network(NetworkPolicy::Allow),
durability: Durability::None,
}
}
#[test]
fn an_environment_with_no_compute_says_nothing() {
let facts = EnvironmentFacts {
kind: None,
..facts(ComputeKind::Vfs)
};
assert!(environment_preamble(&facts).is_none());
}
#[test]
fn the_same_facts_except_the_target_read_differently() {
let container = environment_preamble(&facts(ComputeKind::Container)).expect("preamble");
let managed = environment_preamble(&facts(ComputeKind::Managed)).expect("preamble");
let vfs = environment_preamble(&facts(ComputeKind::Vfs)).expect("preamble");
assert!(container.contains("container"), "{container}");
assert!(managed.contains("provider-managed"), "{managed}");
assert!(vfs.contains("interpreter"), "{vfs}");
assert_ne!(container, managed);
assert_ne!(container, vfs);
}
#[test]
fn a_target_without_native_processes_says_so() {
let facts = EnvironmentFacts {
capabilities: ComputeCapabilities::default(),
..facts(ComputeKind::Vfs)
};
let preamble = environment_preamble(&facts).expect("preamble");
assert!(preamble.contains("cannot"), "{preamble}");
assert!(preamble.contains("native binaries"), "{preamble}");
}
#[test]
fn a_full_machine_states_what_it_can_do_and_claims_no_limits() {
let preamble = environment_preamble(&facts(ComputeKind::Container)).expect("preamble");
assert!(
preamble.contains("You can run native binaries"),
"{preamble}"
);
assert!(
!preamble.contains("You cannot"),
"a full machine has nothing to withhold: {preamble}"
);
}
#[test]
fn containment_levels_read_differently() {
let mut open = facts(ComputeKind::Host);
open.containment = Containment::none();
let open = environment_preamble(&open).expect("preamble");
assert!(open.contains("Nothing isolates"), "{open}");
assert!(
open.contains("destructive to a real machine"),
"an uncontained host must say so: {open}"
);
let isolated = environment_preamble(&facts(ComputeKind::Container)).expect("preamble");
assert!(isolated.contains("host is not reachable"), "{isolated}");
}
#[test]
fn the_network_policy_is_stated() {
let mut denied = facts(ComputeKind::Container);
denied.containment = Containment::isolated().network(NetworkPolicy::Deny);
assert!(
environment_preamble(&denied)
.expect("preamble")
.contains("denied"),
);
let mut allowlisted = facts(ComputeKind::Container);
allowlisted.containment = Containment::isolated().network(NetworkPolicy::Allowlist(vec![
"crates.io".to_string(),
"github.com".to_string(),
]));
let text = environment_preamble(&allowlisted).expect("preamble");
assert!(text.contains("crates.io and github.com"), "{text}");
}
#[test]
fn a_long_allowlist_is_summarised_rather_than_recited() {
let hosts: Vec<String> = (0..40).map(|i| format!("host-{i}.example")).collect();
let mut facts = facts(ComputeKind::Container);
facts.containment = Containment::isolated().network(NetworkPolicy::Allowlist(hosts));
let text = environment_preamble(&facts).expect("preamble");
assert!(text.contains("40 hosts"), "{text}");
assert!(!text.contains("host-7.example"), "{text}");
}
#[test]
fn durability_tells_the_model_what_survives() {
for (durability, expected) in [
(Durability::Checkpointed, "survives losing the compute"),
(Durability::ProviderSnapshot, "not a guarantee"),
(Durability::None, "Nothing recovers"),
] {
let mut facts = facts(ComputeKind::Container);
facts.durability = durability;
let text = environment_preamble(&facts).expect("preamble");
assert!(text.contains(expected), "{durability:?}: {text}");
}
}
#[test]
fn no_provider_tool_name_reaches_the_preamble() {
let forbidden = [
"sandbox_exec",
"sandbox_read_file",
"sandbox_write_file",
"daytona_exec",
"daytona_read_file",
"daytona_create_sandbox",
"read_file",
"write_file",
];
for kind in [
ComputeKind::Host,
ComputeKind::Machine,
ComputeKind::Vfs,
ComputeKind::Container,
ComputeKind::Managed,
] {
let text = environment_preamble(&facts(kind)).expect("preamble");
for name in forbidden {
assert!(
!text.contains(name),
"{kind:?} preamble names the tool {name}: {text}"
);
}
}
}
#[test]
fn clauses_join_readably() {
assert_eq!(join_clauses(&[]), "");
assert_eq!(join_clauses(&["a"]), "a");
assert_eq!(join_clauses(&["a", "b"]), "a and b");
assert_eq!(join_clauses(&["a", "b", "c"]), "a, b, and c");
}
}