use std::path::{Path, PathBuf};
use crate::core::context_admission::{OutputCompleteness, SourceAdmission};
use crate::core::rooted_read::AdmittedRootedRead;
#[derive(Debug)]
pub enum SourceRead {
Admitted(AdmittedRootedRead),
Withheld(String),
Unavailable(String),
}
impl SourceRead {
pub fn text(&self) -> Option<&str> {
match self {
Self::Admitted(read) => Some(&read.content),
Self::Withheld(_) | Self::Unavailable(_) => None,
}
}
pub fn storable(&self) -> Option<&str> {
match self {
Self::Admitted(read) if read.storable && read.complete => Some(&read.content),
Self::Admitted(_) | Self::Withheld(_) | Self::Unavailable(_) => None,
}
}
#[cfg(any(test, feature = "test-support"))]
pub fn withheld(&self) -> Option<&str> {
match self {
Self::Withheld(reason) => Some(reason),
Self::Admitted(_) | Self::Unavailable(_) => None,
}
}
}
pub fn read(path: &str, root: &str, tool: &str, remaining: &mut usize) -> SourceRead {
read_under(path, root, tool, remaining, None)
}
pub fn read_with_admission(
root: &str,
path: &str,
tool: &str,
admission: &crate::core::context_admission::stores::StoreAdmission,
) -> SourceRead {
let mut remaining = crate::core::limits::max_read_bytes();
read_under(path, root, tool, &mut remaining, Some(admission))
}
fn read_under(
path: &str,
root: &str,
tool: &str,
remaining: &mut usize,
gateway: Option<&crate::core::context_admission::stores::StoreAdmission>,
) -> SourceRead {
if root.trim().is_empty() {
return SourceRead::Unavailable(NO_PROJECT_ROOT.to_owned());
}
match crate::core::rooted_read::read_file_for_tool_rooted_admitted_with(
path, root, tool, remaining, gateway,
) {
Ok(read) => SourceRead::Admitted(read),
Err(error) if error.kind() == std::io::ErrorKind::PermissionDenied => {
SourceRead::Withheld(error.to_string())
}
Err(error) => SourceRead::Unavailable(error.to_string()),
}
}
pub fn as_agent_acquisition<T>(root: &str, operation: impl FnOnce() -> T) -> T {
let authority = crate::core::policy::runtime::REQUEST_PROJECT
.try_with(|slot| slot.borrow().clone())
.ok()
.flatten()
.unwrap_or_else(|| PathBuf::from(root));
crate::core::policy::runtime::REQUEST_PROJECT
.sync_scope(std::cell::RefCell::new(Some(authority)), operation)
}
pub fn request_root() -> String {
crate::core::policy::runtime::REQUEST_PROJECT
.try_with(|slot| slot.borrow().clone())
.ok()
.flatten()
.map(|project| project.to_string_lossy().into_owned())
.unwrap_or_default()
}
pub fn serve_originless(text: &str, subject: &str) -> Result<String, String> {
crate::core::context_admission::stored::serve_stored(text, None, None)
.map_err(|withheld| withheld.message(subject))
}
pub fn released_cache_content(
cache: &crate::core::cache::SessionCache,
path: &str,
) -> Option<String> {
cache
.current_full_content(path)
.and_then(Result::ok)
.map(|(text, _)| text)
}
pub fn withheld_line(label: &str, reason: &str) -> String {
format!("[withheld] {label}: {reason}")
}
pub const NO_PROJECT_ROOT: &str = "project root unavailable";
pub fn root_for(path: &str, roots: &[String]) -> String {
let mut named = roots.iter().filter(|root| !root.trim().is_empty());
let Some(first) = named.clone().next() else {
return String::new();
};
if Path::new(path).is_absolute() {
let target = canonical(Path::new(path));
if let Some(containing) = named.find(|root| target.starts_with(canonical(Path::new(root))))
{
return containing.clone();
}
}
first.clone()
}
#[cfg(any(test, feature = "test-support"))]
pub fn fixture_root(path: &str) -> String {
let target = canonical(Path::new(path));
if target.is_dir() {
return target.to_string_lossy().into_owned();
}
target
.parent()
.map_or_else(String::new, |parent| parent.to_string_lossy().into_owned())
}
pub fn canonical(path: &Path) -> PathBuf {
crate::core::pathutil::safe_canonicalize_bounded(path, 2000)
}
fn project_policy_allows(text: &str, complete: bool, tool: &str) -> Result<(), String> {
if let Some(policy) = crate::core::policy::runtime::active() {
let outcome = crate::core::policy::content::evaluate_text(text, &policy);
crate::core::policy::audit_filter::audit_filter(tool, &outcome.audit, outcome.blocked);
if outcome.blocked || !complete {
return Err("source content withheld by policy".to_owned());
}
}
Ok(())
}
pub fn admit_remote_text(text: &str, tool: &str) -> Result<SourceAdmission, String> {
project_policy_allows(text, true, tool)?;
crate::core::context_admission::admit_context_source(text).map_err(|error| error.to_string())
}
pub fn admit_remote_file(
bytes: Vec<u8>,
origin_path: &str,
tool: &str,
) -> Result<SourceAdmission, String> {
if crate::core::binary_detect::has_binary_extension(origin_path)
|| crate::core::text_decode::looks_binary(&bytes[..bytes.len().min(8192)])
{
return Err("source format is not inspectable as text".to_owned());
}
let decoded = crate::core::text_decode::decode_checked(bytes);
let complete = decoded.complete();
project_policy_allows(&decoded.text, complete, tool)?;
crate::core::context_admission::admit_source_with_storage(
&decoded.text,
origin_path,
false,
OutputCompleteness {
decode_lossy: !complete,
..Default::default()
},
)
.inspect(|_| crate::core::context_admission::capture::note_source_id(origin_path))
.map_err(|error| error.to_string())
}
#[cfg(test)]
mod tests {
use super::*;
const AWS_KEY: &str = concat!("AK", "IAIOSFODNN7EXAMPLE");
fn policy(body: &str) -> crate::core::policy::ResolvedPolicy {
crate::core::policy::load(&format!(
"name = \"admitted-sources-test\"\nversion = \"1.0.0\"\ndescription = \"test\"\n{body}"
))
.unwrap()
}
#[test]
fn a_rooted_read_is_sorted_into_admitted_withheld_and_unavailable() {
let _data = crate::core::data_dir::isolated_data_dir();
let _policy = crate::core::policy::runtime::TestPolicyOverride::set(Some(policy(
"[filters]\nclassification = \"block\"\nblocked_labels = [\"CONFIDENTIAL\"]\n",
)));
let project = tempfile::tempdir().unwrap();
let root = project.path().to_string_lossy().into_owned();
let clean = project.path().join("clean.rs");
let blocked = project.path().join("blocked.rs");
std::fs::write(&clean, "pub fn clean() {}\n").unwrap();
std::fs::write(&blocked, "CONFIDENTIAL\npub fn hidden_marker() {}\n").unwrap();
let mut remaining = crate::core::limits::max_read_bytes();
let read = read(
&clean.to_string_lossy(),
&root,
"ctx_outline",
&mut remaining,
);
assert_eq!(read.text(), Some("pub fn clean() {}\n"));
assert_eq!(read.storable(), Some("pub fn clean() {}\n"));
let read = super::read(
&blocked.to_string_lossy(),
&root,
"ctx_outline",
&mut remaining,
);
let reason = read.withheld().expect("a blocked source is withheld");
assert!(!reason.contains("hidden_marker"), "{reason}");
assert!(read.text().is_none() && read.storable().is_none());
let missing = project.path().join("missing.rs");
let read = super::read(
&missing.to_string_lossy(),
&root,
"ctx_outline",
&mut remaining,
);
assert!(read.withheld().is_none() && read.text().is_none());
}
#[test]
fn a_restricted_original_is_delivered_but_never_storable() {
let _data = crate::core::data_dir::isolated_data_dir();
let _community = crate::core::policy::runtime::TestPolicyOverride::set(None);
let project = tempfile::tempdir().unwrap();
let root = project.path().to_string_lossy().into_owned();
let marked = project.path().join("marked.rs");
std::fs::write(&marked, "// TOP SECRET\npub fn plan() {}\n").unwrap();
let mut remaining = crate::core::limits::max_read_bytes();
let read = read(
&marked.to_string_lossy(),
&root,
"ctx_preload",
&mut remaining,
);
assert!(read.text().is_some_and(|text| text.contains("pub fn plan")));
assert!(
read.storable().is_none(),
"a restricted original must not enter a derived store"
);
}
#[test]
fn remote_text_is_admitted_with_its_origin_and_clean_text_keeps_its_bytes() {
let _data = crate::core::data_dir::isolated_data_dir();
let _community = crate::core::policy::runtime::TestPolicyOverride::set(None);
let clean = "# Title\n\nplain remote text\n";
let admitted =
admit_remote_file(clean.as_bytes().to_vec(), "README.md", "ctx_git_read").unwrap();
assert_eq!(admitted.text, clean);
assert!(admitted.storable);
assert_eq!(
admit_remote_text(clean, "ctx_url_read").unwrap().text,
clean
);
let keyed = format!("aws_access_key_id = {AWS_KEY}\n");
let admitted = admit_remote_text(&keyed, "ctx_url_read").unwrap();
assert!(!admitted.text.contains(AWS_KEY), "{}", admitted.text);
let admitted = admit_remote_file(b"PORT=8080\n".to_vec(), ".env", "ctx_git_read").unwrap();
assert!(!admitted.storable);
let binary = admit_remote_file(vec![0x7f, b'E', b'L', b'F', 0, 0, 0, 0], "tool", "x");
assert!(binary.is_err());
}
#[test]
fn remote_text_blocked_by_the_project_policy_is_refused_without_content() {
let _data = crate::core::data_dir::isolated_data_dir();
let _policy = crate::core::policy::runtime::TestPolicyOverride::set(Some(policy(
"[filters]\nclassification = \"block\"\nblocked_labels = [\"CONFIDENTIAL\"]\n",
)));
let reason = admit_remote_text("CONFIDENTIAL\nroadmap-marker\n", "ctx_url_read")
.expect_err("blocked content is refused");
assert!(!reason.contains("roadmap-marker"), "{reason}");
let reason = admit_remote_file(
b"CONFIDENTIAL\nroadmap-marker\n".to_vec(),
"docs/plan.md",
"ctx_git_read",
)
.expect_err("blocked content is refused");
assert!(!reason.contains("roadmap-marker"), "{reason}");
}
#[test]
fn an_agent_acquisition_binds_the_project_and_keeps_one_already_bound() {
let bound = || {
crate::core::policy::runtime::REQUEST_PROJECT
.try_with(|slot| slot.borrow().clone())
.ok()
.flatten()
};
assert_eq!(bound(), None, "no request scope outside an acquisition");
assert_eq!(
as_agent_acquisition("/project/a", bound),
Some(PathBuf::from("/project/a"))
);
let nested =
as_agent_acquisition("/project/a", || as_agent_acquisition("/project/b", bound));
assert_eq!(nested, Some(PathBuf::from("/project/a")));
}
#[test]
fn a_path_is_read_under_the_root_that_contains_it() {
let project = tempfile::tempdir().unwrap();
let other = tempfile::tempdir().unwrap();
let root = project.path().to_string_lossy().into_owned();
let extra = other.path().to_string_lossy().into_owned();
let roots = vec![root.clone(), extra.clone()];
let inside = project.path().join("a.rs");
let in_extra = other.path().join("b.rs");
std::fs::write(&inside, "fn a() {}\n").unwrap();
std::fs::write(&in_extra, "fn b() {}\n").unwrap();
assert_eq!(root_for(&inside.to_string_lossy(), &roots), root);
assert_eq!(root_for(&in_extra.to_string_lossy(), &roots), extra);
assert_eq!(root_for("src/lib.rs", &roots), root);
}
#[test]
fn a_path_outside_every_root_is_not_read() {
let _data = crate::core::data_dir::isolated_data_dir();
let _community = crate::core::policy::runtime::TestPolicyOverride::set(None);
let project = tempfile::tempdir().unwrap();
let elsewhere = tempfile::tempdir().unwrap();
let root = project.path().to_string_lossy().into_owned();
let outside = elsewhere.path().join("notes.txt");
std::fs::write(&outside, "outside-marker\n").unwrap();
let outside = outside.to_string_lossy().into_owned();
assert_eq!(root_for(&outside, std::slice::from_ref(&root)), root);
let mut remaining = crate::core::limits::max_read_bytes();
let refused = read(&outside, &root, "ctx_analyze", &mut remaining);
assert!(refused.text().is_none(), "a path outside the root is read");
let (SourceRead::Withheld(reason) | SourceRead::Unavailable(reason)) = &refused else {
panic!("a path outside the root is read");
};
assert!(!reason.contains("outside-marker"), "{reason}");
assert_eq!(root_for(&outside, &[]), "");
assert_eq!(root_for(&outside, &[String::new()]), "");
let SourceRead::Unavailable(reason) = read(&outside, "", "ctx_analyze", &mut remaining)
else {
panic!("an absent project root must withhold");
};
assert_eq!(reason, NO_PROJECT_ROOT);
}
}