pub mod launchd;
pub mod systemd;
pub mod task;
pub mod task_scheduler;
use crate::error::{OlError, ERR_NO_SUPERVISOR};
pub const ERR_SUPERVISION_UNSUPPORTED_OS: &str = "OL-1950";
pub const ERR_SUPERVISION_INSTALL_FAILED: &str = "OL-1951";
pub const ERR_SUPERVISION_BOOTSTRAP_FAILED: &str = "OL-1952";
pub const ERR_SUPERVISION_CONTROL_FAILED: &str = "OL-1953";
pub const UNIT_VERSION: u32 = 3;
pub fn unit_version_marker() -> String {
format!("openlatch-unit-version: {UNIT_VERSION}")
}
pub fn unit_is_current(contents: &str) -> bool {
contents.contains(&unit_version_marker())
}
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum SupervisorKind {
Launchd,
Systemd,
TaskScheduler,
None,
}
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum SupervisionMode {
Active,
Deferred,
Disabled,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct SupervisionConfig {
pub mode: SupervisionMode,
pub backend: SupervisorKind,
#[serde(skip_serializing_if = "Option::is_none")]
pub disabled_reason: Option<String>,
}
impl Default for SupervisionConfig {
fn default() -> Self {
Self {
mode: SupervisionMode::Disabled,
backend: SupervisorKind::None,
disabled_reason: Some("not_initialized".into()),
}
}
}
pub fn absence_is_deliberate(reason: Option<&str>) -> bool {
matches!(
reason,
Some("user_opt_out" | "foreground_session" | "no_start")
)
}
pub fn unreproducible_environment() -> Vec<&'static str> {
let mut divergent = divergent_directories();
for var in ["OPENLATCH_PORT", "OPENLATCH_BOUNDARY_PORT"] {
if std::env::var_os(var).is_some_and(|v| !v.is_empty()) {
divergent.push(var);
}
}
divergent
}
fn divergent_directories() -> Vec<&'static str> {
let mut divergent = Vec::new();
let canonical =
|p: &std::path::Path| std::fs::canonicalize(p).unwrap_or_else(|_| p.to_path_buf());
if canonical(&crate::config::openlatch_dir())
!= canonical(&crate::config::default_openlatch_dir())
{
divergent.push("OPENLATCH_DIR");
}
if !crate::hooks::claude_code::config_is_machine_global() {
divergent.push("CLAUDE_CONFIG_DIR");
}
divergent
}
pub fn owns_machine_supervision() -> bool {
divergent_directories().is_empty()
}
pub fn unreproducible_environment_error(divergent: &[&'static str]) -> OlError {
OlError::new(
ERR_NO_SUPERVISOR,
format!(
"This install is isolated ({}), and an OS supervisor is machine-global",
divergent.join(", ")
),
)
.with_suggestion(
"The unit carries no environment, so it would supervise the machine's default \
install instead of this one. Run `openlatch supervision enable` from an ordinary \
shell, or leave an isolated instance unsupervised — `openlatch start` is the way \
to bring it up.",
)
.with_docs("https://docs.openlatch.ai/errors/OL-1513")
}
pub trait Supervisor: Send + Sync {
fn kind(&self) -> SupervisorKind;
fn install(&self, binary_path: &std::path::Path) -> Result<(), OlError>;
fn uninstall(&self) -> Result<(), OlError>;
fn status(&self) -> Result<SupervisorStatus, OlError>;
fn start(&self) -> Result<(), OlError>;
fn stop(&self) -> Result<(), OlError>;
fn restart(&self) -> Result<(), OlError>;
}
#[derive(Debug, Clone)]
pub struct SupervisorStatus {
pub installed: bool,
pub running: bool,
pub unit_current: bool,
pub description: String,
}
pub fn select_supervisor() -> Option<Box<dyn Supervisor>> {
select_supervisor_impl()
}
pub fn lifecycle_owner(cfg: &SupervisionConfig) -> Option<Box<dyn Supervisor>> {
if cfg.mode != SupervisionMode::Active {
return None;
}
select_supervisor()
}
#[cfg(target_os = "macos")]
fn select_supervisor_impl() -> Option<Box<dyn Supervisor>> {
Some(Box::new(launchd::LaunchdSupervisor::new()))
}
#[cfg(target_os = "linux")]
fn select_supervisor_impl() -> Option<Box<dyn Supervisor>> {
if systemd::is_systemd_available() {
Some(Box::new(systemd::SystemdSupervisor::new()))
} else {
None
}
}
#[cfg(target_os = "windows")]
fn select_supervisor_impl() -> Option<Box<dyn Supervisor>> {
Some(Box::new(task_scheduler::TaskSchedulerSupervisor::new()))
}
#[cfg(not(any(target_os = "macos", target_os = "linux", target_os = "windows")))]
fn select_supervisor_impl() -> Option<Box<dyn Supervisor>> {
None
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn default_supervision_config_is_disabled() {
let c = SupervisionConfig::default();
assert_eq!(c.mode, SupervisionMode::Disabled);
assert_eq!(c.backend, SupervisorKind::None);
}
#[test]
fn select_supervisor_returns_something_on_this_os() {
let sup = select_supervisor();
#[cfg(any(target_os = "macos", target_os = "windows"))]
assert!(sup.is_some());
let _ = sup;
}
static ENV_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
#[test]
fn an_isolated_install_reports_what_a_unit_cannot_carry() {
let _guard = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner());
let previous = std::env::var_os("OPENLATCH_PORT");
std::env::set_var("OPENLATCH_PORT", "17400");
let divergent = unreproducible_environment();
match previous {
Some(v) => std::env::set_var("OPENLATCH_PORT", v),
None => std::env::remove_var("OPENLATCH_PORT"),
}
assert!(
divergent.contains(&"OPENLATCH_PORT"),
"a port override the unit cannot carry must be reported: {divergent:?}"
);
}
#[test]
fn the_refusal_names_every_divergence_and_a_way_forward() {
let err = unreproducible_environment_error(&["OPENLATCH_DIR", "CLAUDE_CONFIG_DIR"]);
assert_eq!(err.code, ERR_NO_SUPERVISOR);
assert!(err.message.contains("OPENLATCH_DIR"));
assert!(err.message.contains("CLAUDE_CONFIG_DIR"));
assert!(err
.suggestion
.as_deref()
.is_some_and(|s| s.contains("openlatch start")));
}
}