#[cfg(feature = "http-server")]
use std::net::IpAddr;
#[cfg(feature = "http-server")]
use std::path::Path;
#[cfg(feature = "http-server")]
use std::sync::OnceLock;
#[cfg(feature = "http-server")]
use std::sync::atomic::{AtomicU8, Ordering};
#[cfg(feature = "http-server")]
use lean_ctx::core::agents::AgentRegistry;
use lean_ctx::core::archive::delivered;
#[cfg(feature = "http-server")]
const MCP_ENGINE: &str = "mcp";
#[cfg(feature = "http-server")]
const LISTENER_UNKNOWN: u8 = 0;
#[cfg(feature = "http-server")]
const LISTENER_LOOPBACK: u8 = 1;
#[cfg(feature = "http-server")]
const LISTENER_REACHABLE: u8 = 2;
#[cfg(feature = "http-server")]
static LISTENER: AtomicU8 = AtomicU8::new(LISTENER_UNKNOWN);
#[cfg(feature = "http-server")]
fn listener_state(bound: IpAddr) -> u8 {
if bound.is_loopback() {
LISTENER_LOOPBACK
} else {
LISTENER_REACHABLE
}
}
#[cfg(feature = "http-server")]
pub(crate) fn note_listener(bound: IpAddr) {
LISTENER.fetch_max(listener_state(bound), Ordering::Relaxed);
}
#[cfg(feature = "http-server")]
fn serves_local_callers_only() -> bool {
LISTENER.load(Ordering::Relaxed) == LISTENER_LOOPBACK
}
#[cfg(feature = "http-server")]
fn own_executable() -> Option<&'static str> {
static OWN: OnceLock<Option<String>> = OnceLock::new();
OWN.get_or_init(|| {
lean_ctx::ipc::process::identity(std::process::id()).map(|identity| identity.executable)
})
.as_deref()
}
#[cfg(feature = "http-server")]
fn same_project(recorded: &str, project: &Path) -> bool {
let recorded = Path::new(recorded);
recorded.is_absolute()
&& lean_ctx::core::pathutil::canonicalize_secure(recorded)
.is_ok_and(|canonical| canonical == project)
}
#[cfg(feature = "http-server")]
fn engine_live_for(project: &Path) -> bool {
let Some(own) = own_executable() else {
return false;
};
let Some(registry) = AgentRegistry::load() else {
return false;
};
registry.list_active(None).into_iter().any(|agent| {
(agent.agent_type == MCP_ENGINE || agent.transport.is_some())
&& agent
.process_identity
.as_ref()
.is_some_and(|identity| identity.executable == own)
&& same_project(&agent.project_root, project)
})
}
#[cfg(feature = "http-server")]
pub(crate) fn host_store_bound() -> bool {
serves_local_callers_only()
&& super::request_project::current_source_project()
.is_some_and(|project| engine_live_for(&project))
}
pub(crate) fn delivered_for_call(block: &str, call_id: &str) -> bool {
super::request_project::current_source_project().is_some_and(|project| {
super::request_project::in_project(&project, || {
delivered::delivered_to_call(&delivered::digest(block), call_id)
})
})
}
#[cfg(all(test, feature = "http-server"))]
pub(crate) fn engine_delivers(
project: &Path,
file: &str,
content: &str,
block: &str,
call_id: Option<&str>,
) {
let root = project.to_str().expect("a UTF-8 project root");
let path = project.join(file);
std::fs::write(&path, content).expect("source written");
let path = path.to_str().expect("a UTF-8 path");
let recorded = super::request_project::in_project(project, || {
let admitted =
lean_ctx::core::rooted_read::read_file_for_tool_rooted(path, root, "ctx_read")
.expect("admitted read");
let authority = lean_ctx::core::archive::authority::ArchiveAuthority::file(
project, path, "ctx_read", &admitted,
)
.expect("protection is active: the read is bound");
delivered::record_tool_delivery_for_call(true, false, Some(authority), &[block], call_id)
});
assert_eq!(recorded, 1, "the delivery is on record");
}
#[cfg(all(test, feature = "http-server"))]
#[must_use]
pub(crate) struct ListenerForTest {
replaced: u8,
}
#[cfg(all(test, feature = "http-server"))]
impl Drop for ListenerForTest {
fn drop(&mut self) {
LISTENER.store(self.replaced, Ordering::Relaxed);
}
}
#[cfg(all(test, feature = "http-server"))]
pub(crate) fn listening_on(bound: IpAddr) -> ListenerForTest {
ListenerForTest {
replaced: LISTENER.swap(listener_state(bound), Ordering::Relaxed),
}
}
#[cfg(all(test, feature = "http-server"))]
const ENGINE_PROCESS_VAR: &str = "LEAN_CTX_TEST_ENGINE_PROCESS";
#[cfg(all(test, feature = "http-server"))]
struct EngineProcessForTest(std::process::Child);
#[cfg(all(test, feature = "http-server"))]
impl EngineProcessForTest {
fn start() -> Self {
use std::process::{Command, Stdio};
let child = Command::new(std::env::current_exe().expect("test executable"))
.args([
"--exact",
"store_binding::tests::engine_process_for_test",
"--test-threads=1",
"--quiet",
])
.env(ENGINE_PROCESS_VAR, "1")
.stdin(Stdio::piped())
.stdout(Stdio::null())
.stderr(Stdio::null())
.spawn()
.expect("the Engine process starts");
Self(child)
}
}
#[cfg(all(test, feature = "http-server"))]
impl Drop for EngineProcessForTest {
fn drop(&mut self) {
let _ = self.0.kill();
let _ = self.0.wait();
}
}
#[cfg(all(test, feature = "http-server"))]
#[must_use]
pub(crate) struct BoundHostForTest {
_listener: ListenerForTest,
_engine: EngineProcessForTest,
}
#[cfg(all(test, feature = "http-server"))]
pub(crate) fn bound_host(project: &Path) -> BoundHostForTest {
let engine = EngineProcessForTest::start();
let identity =
lean_ctx::ipc::process::identity(engine.0.id()).expect("Engine process identity");
assert_eq!(
Some(identity.executable.as_str()),
own_executable(),
"the Engine process runs this installation's executable"
);
engine_on_record(engine.0.id(), identity, project);
BoundHostForTest {
_listener: listening_on(IpAddr::V4(std::net::Ipv4Addr::LOCALHOST)),
_engine: engine,
}
}
#[cfg(all(test, feature = "http-server"))]
pub(crate) fn registry_file() -> std::path::PathBuf {
lean_ctx::core::data_dir::lean_ctx_data_dir()
.expect("data directory")
.join("agents")
.join("registry.json")
}
#[cfg(all(test, feature = "http-server"))]
pub(crate) fn engine_on_record(
pid: u32,
identity: lean_ctx::ipc::process::ProcessIdentity,
project: &Path,
) {
let now = chrono::Utc::now();
let entry = lean_ctx::core::agents::AgentEntry {
agent_id: format!("mcp-{pid}-test"),
agent_type: MCP_ENGINE.to_owned(),
role: Some("context-engine".to_owned()),
project_root: project.to_str().expect("a UTF-8 project root").to_owned(),
started_at: now,
last_active: now,
pid,
process_identity: Some(identity),
durable_identity_id: None,
status: lean_ctx::core::agents::AgentStatus::Active,
status_message: None,
transport: None,
role_source: None,
parent: None,
app_session_id: None,
display_name: None,
};
AgentRegistry::mutate_locked(|registry| registry.agents.push(entry)).expect("registry saved");
}
#[cfg(all(test, unix, feature = "http-server"))]
pub(crate) fn exited_engine_on_record(project: &Path) {
use std::process::{Command, Stdio};
let mut child = Command::new("sleep")
.arg("60")
.stdin(Stdio::null())
.stdout(Stdio::null())
.stderr(Stdio::null())
.spawn()
.expect("process starts");
let mut identity = lean_ctx::ipc::process::identity(child.id()).expect("child identity");
own_executable()
.expect("own executable")
.clone_into(&mut identity.executable);
child.kill().expect("child killed");
child.wait().expect("child reaped");
engine_on_record(child.id(), identity, project);
}
#[cfg(all(test, feature = "http-server"))]
mod tests {
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
use std::path::Path;
use std::sync::atomic::Ordering;
use super::{
LISTENER, LISTENER_UNKNOWN, bound_host, delivered_for_call, engine_delivers,
engine_live_for, host_store_bound, listening_on, note_listener, own_executable,
registry_file, same_project, serves_local_callers_only,
};
use lean_ctx::core::agents::AgentRegistry;
fn project() -> (tempfile::TempDir, std::path::PathBuf) {
let directory = tempfile::tempdir().expect("project");
let root = lean_ctx::core::pathutil::canonicalize_secure(directory.path())
.expect("canonical root");
(directory, root)
}
fn governed_project(root: &Path) {
let config = lean_ctx::core::paths::config_dir_read_only().expect("config directory");
std::fs::create_dir_all(&config).expect("config directory");
std::fs::write(
config.join("config.toml"),
format!(
"[context_gateway]\nmode = \"governed\"\n\n[proxy]\nproject_root = '{}'\n",
root.display()
),
)
.expect("gateway config");
}
#[test]
fn engine_process_for_test() {
if std::env::var_os(super::ENGINE_PROCESS_VAR).is_none() {
return;
}
let _ = std::io::copy(&mut std::io::stdin(), &mut std::io::sink());
}
#[test]
fn a_bound_host_survives_servers_of_this_process_registering_elsewhere() {
let _data = lean_ctx::core::data_dir::isolated_data_dir();
let _community = lean_ctx::core::policy::runtime::TestPolicyOverride::set(None);
let (_directory, root) = project();
let (_other_directory, other) = project();
governed_project(&root);
let _bound = bound_host(&root);
assert!(host_store_bound());
AgentRegistry::register_mcp_process(other.to_str().expect("utf-8"), None)
.expect("this process registers for another project");
assert!(engine_live_for(&other));
assert!(
engine_live_for(&root),
"the bound host's Engine is untouched"
);
assert!(host_store_bound());
}
#[test]
fn only_a_loopback_listener_serves_local_callers_only() {
let _data = lean_ctx::core::data_dir::isolated_data_dir();
let _community = lean_ctx::core::policy::runtime::TestPolicyOverride::set(None);
LISTENER.store(LISTENER_UNKNOWN, Ordering::Relaxed);
assert!(!serves_local_callers_only(), "unknown");
note_listener(IpAddr::V4(Ipv4Addr::LOCALHOST));
assert!(serves_local_callers_only());
note_listener(IpAddr::V6(Ipv6Addr::LOCALHOST));
assert!(serves_local_callers_only());
note_listener(IpAddr::V4(Ipv4Addr::UNSPECIFIED));
assert!(!serves_local_callers_only(), "all interfaces");
note_listener(IpAddr::V4(Ipv4Addr::LOCALHOST));
assert!(!serves_local_callers_only(), "never lowered");
LISTENER.store(LISTENER_UNKNOWN, Ordering::Relaxed);
for reachable in [
IpAddr::V4(Ipv4Addr::new(192, 168, 1, 20)),
IpAddr::V6(Ipv6Addr::UNSPECIFIED),
] {
let _listener = listening_on(reachable);
assert!(!serves_local_callers_only(), "{reachable}");
}
assert!(!serves_local_callers_only(), "unknown again after a test");
}
#[test]
fn a_live_engine_of_this_store_and_project_binds_the_host() {
let _data = lean_ctx::core::data_dir::isolated_data_dir();
let _community = lean_ctx::core::policy::runtime::TestPolicyOverride::set(None);
let (_directory, root) = project();
let (_other_directory, other) = project();
assert!(own_executable().is_some(), "this process has an identity");
{
let _bound = bound_host(&root);
assert!(engine_live_for(&root));
assert!(!host_store_bound(), "the proxy acts for no project");
}
governed_project(&root);
std::fs::remove_file(registry_file()).expect("registry removed");
let loopback = listening_on(IpAddr::V4(Ipv4Addr::LOCALHOST));
assert!(!engine_live_for(&root), "no registry");
assert!(!host_store_bound());
AgentRegistry::register_mcp_process(other.to_str().expect("utf-8"), None)
.expect("registered");
assert!(engine_live_for(&other));
assert!(!engine_live_for(&root), "an Engine for another project");
assert!(!host_store_bound());
std::fs::remove_file(registry_file()).expect("registry removed");
AgentRegistry::mutate_locked(|registry| {
registry.register("claude", Some("coder"), root.to_str().expect("utf-8"), None)
})
.expect("registry written")
.1
.expect("registered");
assert!(!engine_live_for(&root), "not an MCP Engine");
assert!(!host_store_bound());
AgentRegistry::register_mcp_process(root.to_str().expect("utf-8"), None)
.expect("registered");
assert!(engine_live_for(&root));
assert!(host_store_bound());
let block = "pub fn kept() {}\n";
assert!(!delivered_for_call(block, "toolu_X"), "nothing on record");
engine_delivers(&root, "other.rs", block, block, None);
assert!(!delivered_for_call(block, "toolu_X"), "a digest hit alone");
engine_delivers(&root, "kept.rs", block, block, Some("toolu_X"));
assert!(delivered_for_call(block, "toolu_X"));
assert!(!delivered_for_call(block, "toolu_Y"), "another call");
assert!(!delivered_for_call("other bytes", "toolu_X"));
drop(loopback);
assert!(!host_store_bound(), "listener unknown");
{
let _reachable = listening_on(IpAddr::V4(Ipv4Addr::UNSPECIFIED));
assert!(engine_live_for(&root));
assert!(!host_store_bound(), "not loopback");
}
let _loopback = listening_on(IpAddr::V4(Ipv4Addr::LOCALHOST));
assert!(host_store_bound());
std::fs::write(registry_file(), "{ not a registry").expect("registry overwritten");
assert!(!engine_live_for(&root), "malformed registry");
assert!(!host_store_bound());
}
#[test]
fn the_recorded_root_is_compared_canonically() {
let _data = lean_ctx::core::data_dir::isolated_data_dir();
let _community = lean_ctx::core::policy::runtime::TestPolicyOverride::set(None);
let (directory, root) = project();
let nested = root.join("nested");
std::fs::create_dir(&nested).expect("nested directory");
let roundabout = nested.join("..");
assert!(same_project(root.to_str().expect("utf-8"), &root));
assert!(same_project(
directory.path().to_str().expect("utf-8"),
&root
));
assert!(same_project(roundabout.to_str().expect("utf-8"), &root));
assert!(!same_project(nested.to_str().expect("utf-8"), &root));
assert!(!same_project(".", &root), "relative");
assert!(!same_project("", &root));
assert!(!same_project(
root.join("missing").to_str().expect("utf-8"),
&root
));
AgentRegistry::register_mcp_process(roundabout.to_str().expect("utf-8"), None)
.expect("registered");
assert!(engine_live_for(&root), "a non-canonical spelling binds");
}
#[cfg(unix)]
#[test]
fn a_dead_or_foreign_process_binds_nothing() {
use std::process::{Command, Stdio};
use super::{engine_on_record, exited_engine_on_record};
let _data = lean_ctx::core::data_dir::isolated_data_dir();
let _community = lean_ctx::core::policy::runtime::TestPolicyOverride::set(None);
let (_directory, root) = project();
let mut child = Command::new("sleep")
.arg("60")
.stdin(Stdio::null())
.stdout(Stdio::null())
.stderr(Stdio::null())
.spawn()
.expect("process starts");
let own = std::env::current_exe().expect("test binary");
let deadline = std::time::Instant::now() + std::time::Duration::from_secs(5);
let identity = loop {
let identity = lean_ctx::ipc::process::identity(child.id()).expect("child identity");
if std::path::Path::new(&identity.executable) != own
|| std::time::Instant::now() >= deadline
{
break identity;
}
std::thread::sleep(std::time::Duration::from_millis(10));
};
engine_on_record(child.id(), identity, &root);
let live = (0..250).any(|_| {
let live = AgentRegistry::load()
.expect("registry")
.list_active(None)
.iter()
.any(|agent| agent.pid == child.id());
if !live {
std::thread::sleep(std::time::Duration::from_millis(20));
}
live
});
let bound_while_live = engine_live_for(&root);
child.kill().expect("child killed");
child.wait().expect("child reaped");
assert!(live, "live by the registry's rule while it ran");
assert!(!bound_while_live, "a live process of another executable");
assert!(!engine_live_for(&root), "and nothing once it has exited");
std::fs::remove_file(registry_file()).expect("registry removed");
exited_engine_on_record(&root);
let registry = AgentRegistry::load().expect("registry");
assert_eq!(registry.list_all().len(), 1, "the entry is on record");
assert!(registry.list_active(None).is_empty(), "and not live");
assert!(!engine_live_for(&root), "an exited Engine");
}
}