use crate::cli::output::{OutputConfig, OutputFormat};
use crate::cli::{BoundaryCommands, BoundaryToggleArgs};
use crate::error::{OlError, ERR_BOUNDARY_FINDING_NOT_FOUND};
pub fn run(cmd: &BoundaryCommands, output: &OutputConfig) -> Result<(), OlError> {
match cmd {
BoundaryCommands::Status => status(output),
BoundaryCommands::Enable(args) => toggle(true, args, output),
BoundaryCommands::Disable(args) => toggle(false, args, output),
BoundaryCommands::Explain { finding_id } => explain(finding_id, output),
}
}
fn toggle(enable: bool, args: &BoundaryToggleArgs, output: &OutputConfig) -> Result<(), OlError> {
use std::io::{BufRead, IsTerminal, Write};
let config_path = crate::config::openlatch_dir().join("config.toml");
if !config_path.exists() {
crate::config::ensure_config(crate::config::Config::defaults().port)?;
}
let before = crate::config::Config::load(None, None, false)
.map(|c| c.boundary.enabled)
.unwrap_or(true);
let verb = if enable { "enabled" } else { "disabled" };
crate::cli::header::print(
output,
&["boundary", if enable { "enable" } else { "disable" }],
);
if before == enable {
output.print_substep(&format!("Model boundary already {verb} in config"));
} else {
crate::config::persist_boundary_enabled(&config_path, enable)?;
output.print_step(&format!(
"Model boundary {verb} in {}",
config_path.display()
));
}
let daemon_up = crate::cli::commands::lifecycle::read_pid_file()
.map(crate::cli::commands::lifecycle::is_process_alive)
.unwrap_or(false);
if !daemon_up {
output.print_step("No daemon running — the change applies at the next start");
emit_toggle_json(enable, true, false, true, output);
return Ok(());
}
let interactive =
std::io::stdin().is_terminal() && output.format == OutputFormat::Human && !output.quiet;
let restart = if args.yes {
true
} else if args.no_restart || !interactive {
false
} else {
eprint!("Restart the daemon now to apply? [y/N] ");
let _ = std::io::stderr().flush();
let mut answer = String::new();
let _ = std::io::stdin().lock().read_line(&mut answer);
matches!(answer.trim().to_ascii_lowercase().as_str(), "y" | "yes")
};
if !restart {
output.print_substep(
"Config updated — not in effect until the daemon restarts (run `openlatch restart`)",
);
emit_toggle_json(enable, true, false, false, output);
crate::cli::report::record_exit_code(crate::cli::report::EXIT_DEGRADED);
return Ok(());
}
crate::cli::commands::lifecycle::run_restart(output)?;
let cfg = crate::config::Config::load(None, None, false)?;
let in_effect = boundary_rows(&cfg).iter().all(|row| match row.state {
BoundaryState::Disabled => !enable,
BoundaryState::Wired | BoundaryState::Isolated => enable,
_ => false,
});
if in_effect {
output.print_step(&format!("Model boundary {verb} and in effect"));
} else {
output.print_substep(
"Daemon restarted, but the boundary is not in the requested state — run \
`openlatch doctor` for the reason",
);
crate::cli::report::record_exit_code(crate::cli::report::EXIT_DEGRADED);
}
emit_toggle_json(enable, true, true, in_effect, output);
Ok(())
}
fn emit_toggle_json(
enabled: bool,
config_written: bool,
restarted: bool,
in_effect: bool,
output: &OutputConfig,
) {
if output.format != OutputFormat::Json {
return;
}
output.print_json(&serde_json::json!({
"enabled": enabled,
"config_written": config_written,
"restarted": restarted,
"in_effect": in_effect,
"exit_code": if in_effect { 0 } else { crate::cli::report::EXIT_DEGRADED },
}));
}
#[derive(Debug, PartialEq)]
pub(crate) enum PortOwnership {
Owned,
Foreign,
Unreachable,
}
const CONNECT_TIMEOUT: std::time::Duration = std::time::Duration::from_millis(100);
const PROBE_TIMEOUT: std::time::Duration = std::time::Duration::from_millis(500);
fn get_admin_status(port: u16) -> Option<reqwest::blocking::Response> {
let url = format!("http://127.0.0.1:{port}/admin/boundary/status");
crate::egress::blocking_client_builder()
.timeout(PROBE_TIMEOUT)
.build()
.ok()?
.get(&url)
.send()
.ok()
}
pub(crate) fn verify_port_ownership(port: u16) -> PortOwnership {
let addr = std::net::SocketAddr::from(([127, 0, 0, 1], port));
if std::net::TcpStream::connect_timeout(&addr, CONNECT_TIMEOUT).is_err() {
return PortOwnership::Unreachable;
}
let Some(resp) = get_admin_status(port) else {
return PortOwnership::Foreign;
};
if !resp.status().is_success() {
return PortOwnership::Foreign;
}
match resp.json::<serde_json::Value>() {
Ok(v) if v.get("status").is_some() && v.get("upstream").is_some() => PortOwnership::Owned,
_ => PortOwnership::Foreign,
}
}
pub fn status(output: &OutputConfig) -> Result<(), OlError> {
let cfg = crate::config::Config::load(None, None, false)?;
let port = cfg.boundary.port;
let rows = boundary_rows(&cfg);
let probe = probe_boundary(port);
let exit = match boundary_severity(&worst_row(&rows).state) {
0 => 0,
2 => 1,
_ => crate::cli::report::EXIT_DEGRADED,
};
crate::cli::report::record_exit_code(exit);
if output.format == OutputFormat::Json {
output.print_json(&serde_json::json!({
"port": port,
"state": worst_row(&rows).state.label(),
"upstream": crate::boundary::wire_format::WireFormat::ALL
.iter()
.map(|f| (f.as_str().to_string(), serde_json::json!(cfg.boundary.upstream_for(*f))))
.collect::<serde_json::Map<_, _>>(),
"classification": format!("{:?}", worst_row(&rows).state),
"enabled": cfg.boundary.enabled,
"owns_agent_wiring": cfg.boundary.owns_agent_wiring(),
"wired_to": worst_row(&rows).wired,
"agents": rows
.iter()
.filter(|r| !r.agent.is_empty())
.map(|r| serde_json::json!({
"agent": r.agent,
"state": r.state.label(),
"classification": format!("{:?}", r.state),
"wired_to": r.wired,
}))
.collect::<Vec<_>>(),
"up": probe.is_some(),
"detail": probe,
"exit_code": exit,
}));
return Ok(());
}
crate::cli::header::print(output, &["boundary status"]);
let multi = rows.len() > 1;
for row in &rows {
let wired = row.wired.clone();
let (line, remedy): (String, Option<String>) = match &row.state {
BoundaryState::Disabled => (
"Boundary: disabled in config — model calls bypass OpenLatch".to_string(),
Some("Run `openlatch boundary enable` to turn it back on.".to_string()),
),
BoundaryState::Isolated => (
format!("Boundary: isolated instance on port {port}"),
Some(format!(
"This instance does not touch the machine-global agent config. Route a session \
through it with:\n ANTHROPIC_BASE_URL=http://127.0.0.1:{port} claude"
)),
),
BoundaryState::Wired => (
format!(
"Boundary: up on port {port}, agent wired to {}",
wired.as_deref().unwrap_or("it")
),
None,
),
BoundaryState::WiredButDown => (
format!("Boundary: agent is wired to 127.0.0.1:{port} but nothing is listening"),
Some(
"Model calls fail with ECONNREFUSED. Run `openlatch start` to bring the \
listener up, or `openlatch stop` to clear the wiring and go direct."
.to_string(),
),
),
BoundaryState::WiredToForeign => (
format!("Boundary: 127.0.0.1:{port} is held by a process that is NOT OpenLatch"),
Some(format!(
"The agent is wired to it, so your provider API key is going to that process. \
Identify it (lsof -i :{port}), stop it, then run `openlatch restart`."
)),
),
BoundaryState::PreflightFailed(why) => (
format!("Boundary: up on port {port}, preflight FAILED — {why}"),
Some(
"The agent was left unwired on purpose: model calls go direct and keep \
working, but nothing is captured. Fix reachability to the provider; the \
daemon re-wires itself as soon as the check passes."
.to_string(),
),
),
BoundaryState::PreflightPending => (
format!("Boundary: up on port {port}, preflight still running"),
Some("The agent is wired once it passes. Re-run this in a moment.".to_string()),
),
BoundaryState::UpUnwired => (
format!("Boundary: up on port {port} but the agent is not wired to it"),
Some("Run `openlatch restart` to re-wire.".to_string()),
),
BoundaryState::Down => (
format!("Boundary: enabled in config, nothing listening on port {port}"),
Some("Run `openlatch start`.".to_string()),
),
BoundaryState::ForeignIdle => (
format!("Boundary: 127.0.0.1:{port} is held by another process"),
Some(format!(
"The agent is not wired to it, but the next `openlatch start` will refuse to \
bind. Identify it with `lsof -i :{port}`."
)),
),
};
if multi {
eprintln!(" [{}]", row.display_name);
}
eprintln!(" {line}");
if let Some(remedy) = remedy {
eprintln!(" {remedy}");
}
}
if let Some(v) = probe.as_ref() {
if let Some(up) = v.get("upstream").and_then(|x| x.as_object()) {
for (fmt, base) in up {
if let Some(base) = base.as_str() {
eprintln!(" Upstream ({fmt}): {base}");
}
}
}
if let Some(base) = v.get("upstream_chatgpt").and_then(|x| x.as_str()) {
eprintln!(" Upstream (openai-responses, ChatGPT plan): {base}");
}
if let Some(f) = v.get("pass_through_failures").and_then(|x| x.as_u64()) {
eprintln!(" Pass-through failures: {f}");
}
}
Ok(())
}
pub fn explain(finding_id: &str, output: &OutputConfig) -> Result<(), OlError> {
let record = crate::boundary::retention::load(finding_id).ok_or_else(|| {
OlError::new(
ERR_BOUNDARY_FINDING_NOT_FOUND,
format!("no local churn finding '{finding_id}'"),
)
.with_suggestion(
"Findings resolve only on the host that produced them, and expire from the bounded \
local store. Check the id from the `ai.openlatch.prefix.finding_id` field.",
)
})?;
if output.format == OutputFormat::Json {
output.print_json(&serde_json::json!({
"finding_id": record.finding_id,
"captured_at": record.captured_at,
"churn_layer": record.churn_layer,
"churn_class": record.churn_class,
"divergence_offset": record.divergence_offset,
"churn_byte_len": record.churn_byte_len,
"churn_block_index": record.churn_block_index,
"block": record.block,
}));
} else {
crate::cli::header::print(output, &["boundary explain"]);
eprintln!(" finding : {}", record.finding_id);
eprintln!(" captured : {}", record.captured_at);
eprintln!(" layer : {}", record.churn_layer);
eprintln!(" class : {}", record.churn_class);
eprintln!(
" offset/len : {} / {} (block #{})",
record.divergence_offset, record.churn_byte_len, record.churn_block_index
);
eprintln!(" block (local, never emitted):");
println!("{}", record.block);
}
Ok(())
}
pub fn probe_boundary(port: u16) -> Option<serde_json::Value> {
let resp = get_admin_status(port)?;
if !resp.status().is_success() {
return None;
}
resp.json().ok()
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) enum BoundaryState {
Disabled,
Isolated,
Wired,
WiredButDown,
WiredToForeign,
PreflightFailed(String),
PreflightPending,
UpUnwired,
Down,
ForeignIdle,
}
impl BoundaryState {
pub(crate) fn label(&self) -> &'static str {
match self {
BoundaryState::Disabled => "disabled",
BoundaryState::Isolated => "isolated",
BoundaryState::Wired => "up",
BoundaryState::WiredButDown => "down",
BoundaryState::WiredToForeign => "failed",
BoundaryState::PreflightFailed(_) => "preflight-failed",
BoundaryState::PreflightPending => "preflight-pending",
BoundaryState::UpUnwired => "unwired",
BoundaryState::Down => "down",
BoundaryState::ForeignIdle => "down",
}
}
}
pub(crate) struct AgentBoundary {
pub agent: &'static str,
pub display_name: &'static str,
pub wired: Option<String>,
pub state: BoundaryState,
}
pub(crate) fn boundary_rows(cfg: &crate::config::Config) -> Vec<AgentBoundary> {
let mut rows: Vec<AgentBoundary> = crate::hooks::detect_agents()
.into_iter()
.filter(|a| a.binding.boundary_wiring().is_some())
.map(|a| {
let wired = read_agent_wiring(&*a.binding);
let state = classify_boundary(cfg, a.agent_type(), wired.as_deref());
AgentBoundary {
agent: a.agent_type(),
display_name: a.binding.display_name(),
wired,
state,
}
})
.collect();
if rows.is_empty() {
rows.push(AgentBoundary {
agent: "",
display_name: "The agent",
wired: None,
state: classify_boundary(cfg, "", None),
});
}
rows
}
pub(crate) fn boundary_severity(state: &BoundaryState) -> u8 {
match state {
BoundaryState::Wired | BoundaryState::Isolated => 0,
BoundaryState::Disabled
| BoundaryState::PreflightPending
| BoundaryState::UpUnwired
| BoundaryState::Down => 1,
BoundaryState::WiredButDown
| BoundaryState::WiredToForeign
| BoundaryState::PreflightFailed(_)
| BoundaryState::ForeignIdle => 2,
}
}
pub(crate) fn worst_row(rows: &[AgentBoundary]) -> &AgentBoundary {
rows.iter()
.max_by_key(|r| boundary_severity(&r.state))
.expect("boundary_rows never returns an empty list")
}
pub(crate) fn classify_boundary(
cfg: &crate::config::Config,
agent: &'static str,
wired: Option<&str>,
) -> BoundaryState {
if !cfg.boundary.enabled {
return BoundaryState::Disabled;
}
if !cfg.boundary.owns_agent_wiring() {
return BoundaryState::Isolated;
}
let port = cfg.boundary.port;
match (wired, verify_port_ownership(port)) {
(Some(_), PortOwnership::Owned) => BoundaryState::Wired,
(Some(_), PortOwnership::Unreachable) => BoundaryState::WiredButDown,
(Some(_), PortOwnership::Foreign) => BoundaryState::WiredToForeign,
(None, PortOwnership::Owned) => {
let live = probe_boundary(port);
match live
.as_ref()
.and_then(|v| v.get("preflight"))
.and_then(|v| v.get(agent))
.and_then(|v| v.as_str())
{
Some("failed") => BoundaryState::PreflightFailed(
live.as_ref()
.and_then(|v| v.get("preflight_error"))
.and_then(|v| v.get(agent))
.and_then(|v| v.as_str())
.unwrap_or("no round trip to the provider completed")
.to_string(),
),
Some("pending") => BoundaryState::PreflightPending,
_ => BoundaryState::UpUnwired,
}
}
(None, PortOwnership::Unreachable) => BoundaryState::Down,
(None, PortOwnership::Foreign) => BoundaryState::ForeignIdle,
}
}
pub(crate) fn read_agent_wiring(
binding: &dyn crate::hooks::binding::AgentBinding,
) -> Option<String> {
use crate::hooks::binding::EndpointConvention;
match binding.boundary_wiring()?.endpoint {
EndpointConvention::EnvVars { .. } => read_boundary_base_url(&binding.hook_config_path()),
EndpointConvention::TomlProvider { provider_name, .. } => {
crate::hooks::codex_cli::read_provider_base_url(
&crate::hooks::codex_cli::config_toml_path(&binding.config_dir()),
provider_name,
)
}
}
}
pub(crate) fn read_boundary_base_url(settings_path: &std::path::Path) -> Option<String> {
let raw = std::fs::read_to_string(settings_path).ok()?;
let parsed = crate::hooks::jsonc::parse_settings_value(&raw).ok()?;
let url = parsed
.get("env")?
.get("ANTHROPIC_BASE_URL")?
.as_str()?
.to_string();
reqwest::Url::parse(url.trim())
.ok()
.filter(|u| u.host_str() == Some("127.0.0.1"))
.map(|_| url)
}
#[cfg(test)]
mod tests {
use super::{verify_port_ownership, PortOwnership};
#[test]
fn verify_port_ownership_refuses_closed_and_foreign_ports() {
let l = std::net::TcpListener::bind("127.0.0.1:0").unwrap();
let closed = l.local_addr().unwrap().port();
drop(l);
assert_eq!(verify_port_ownership(closed), PortOwnership::Unreachable);
let listener = std::net::TcpListener::bind("127.0.0.1:0").unwrap();
let foreign = listener.local_addr().unwrap().port();
std::thread::spawn(move || {
use std::io::{Read, Write};
for mut s in listener.incoming().flatten() {
let mut buf = [0u8; 1024];
let _ = s.read(&mut buf);
let body = br#"{"foo":"bar"}"#;
let head = format!(
"HTTP/1.1 200 OK\r\nContent-Type: application/json\r\n\
Content-Length: {}\r\nConnection: close\r\n\r\n",
body.len()
);
let _ = s.write_all(head.as_bytes());
let _ = s.write_all(body);
let _ = s.flush();
}
});
std::thread::sleep(std::time::Duration::from_millis(50));
assert_eq!(verify_port_ownership(foreign), PortOwnership::Foreign);
}
#[test]
fn disabled_in_config_short_circuits_before_any_probe() {
use crate::cli::commands::boundary::{classify_boundary, BoundaryState};
let mut cfg = crate::config::Config::defaults();
cfg.boundary.enabled = false;
assert_eq!(
classify_boundary(&cfg, "claude-code", None),
BoundaryState::Disabled
);
assert_eq!(
classify_boundary(&cfg, "claude-code", Some("http://127.0.0.1:7600")),
BoundaryState::Disabled
);
assert_eq!(BoundaryState::Disabled.label(), "disabled");
}
#[test]
fn isolated_instance_short_circuits_before_any_probe() {
use crate::cli::commands::boundary::{classify_boundary, BoundaryState};
let mut cfg = crate::config::Config::defaults();
cfg.boundary.enabled = true;
cfg.boundary.port = crate::boundary::default_boundary_port() + 1;
assert_eq!(
classify_boundary(&cfg, "claude-code", None),
BoundaryState::Isolated
);
}
}