Skip to main content

supercode_harness/
orchestrator.rs

1//! The orchestrator's home, its daemon lease, and the service unit the
2//! operator verbs print (ORC-7).
3//!
4//! supercode does not perform orchestration; the `supercode-orchestrator`
5//! package does (`docs/ORCHESTRATOR-IR.md` §0.1). This module holds the three
6//! facts supercode's own surfaces need about it:
7//!
8//! * **where its state lives** — `SUPERCODE_ORCHESTRATOR_HOME`, default
9//!   `~/.supercode/orchestrator`, resolved once in
10//!   [`crate::HarnessHomes::orchestrator`]; the root folder IS the `default`
11//!   profile and `profiles/<name>/` are the named ones
12//!   (`docs/ORCHESTRATOR-IR.md` §6).
13//! * **whether the daemon is up** — the lease file `<home>/orchestrator.lock`,
14//!   plus a liveness signal on the pid it names. A lock whose process is gone
15//!   is stale, never "up".
16//! * **what a service unit for it would say, and whether it is installed** —
17//!   [`service_unit`] renders the launchd plist / systemd unit `setup` writes
18//!   under `<home>/service/`; [`install_service`], [`uninstall_service`] and
19//!   [`service_status`] drive `launchctl` / `systemctl --user` over it.
20//!
21//! The lease FILE is written by the daemon itself (`bin/orchestrator.mjs`), not
22//! by this crate: one writer means a service-managed daemon reports the same
23//! lease a foreground one does, and a lock left by a process that is gone is
24//! the daemon's own signal to replay (§4.7).
25//!
26//! Reading the folder is every existing ORCH reader's job: `jobs`, `runs`,
27//! `routes`, `channels`, `triggers`, `profiles` and session discovery point
28//! their Hermes-shaped code paths at these same profile folders.
29
30use std::path::{Path, PathBuf};
31
32use serde::{Deserialize, Serialize};
33
34/// Lease file the daemon writes while it serves a home, relative to the home.
35pub const LOCK_FILE: &str = "orchestrator.lock";
36
37/// Directory `setup` writes the rendered service unit into.
38pub const SERVICE_DIR: &str = "service";
39
40/// The daemon entry inside the `sdk/orchestrator` package.
41pub const DAEMON_ENTRY: &str = "bin/orchestrator.mjs";
42
43/// launchd label / systemd unit name for the orchestrator daemon.
44pub const SERVICE_NAME: &str = "ai.volter.supercode.orchestrator";
45
46/// The lease `<home>/orchestrator.lock` holds: which process is serving this
47/// home, and since when.
48#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
49pub struct Lease {
50    /// Daemon process id.
51    pub pid: u32,
52    /// RFC3339 instant the daemon recorded at startup.
53    pub started_at: String,
54    /// The home the daemon was started against.
55    pub root: PathBuf,
56    /// The machine the daemon runs on. A home on a volume another machine
57    /// mounted names that machine's pid, which says nothing about this one.
58    #[serde(default, skip_serializing_if = "Option::is_none")]
59    pub host: Option<String>,
60    /// That machine's boot (Linux's `boot_id`), where it has one: a pid from
61    /// before a restart names nothing now.
62    #[serde(default, skip_serializing_if = "Option::is_none")]
63    pub boot: Option<String>,
64}
65
66impl Lease {
67    /// Whether the daemon this lease names is alive: on this machine, since
68    /// this boot, and its pid a live process. A lease from elsewhere is never
69    /// live here, so nothing here is signalled on its word.
70    pub fn is_live(&self) -> bool {
71        let here = this_host();
72        if self
73            .host
74            .as_deref()
75            .is_some_and(|host| Some(host) != here.0.as_deref())
76        {
77            return false;
78        }
79        if let (Some(boot), Some(now)) = (self.boot.as_deref(), here.1.as_deref()) {
80            if boot != now {
81                return false;
82            }
83        }
84        pid_is_live(self.pid)
85    }
86}
87
88/// This machine's name and, on Linux, its boot id: what a lease is compared with.
89pub fn this_host() -> (Option<String>, Option<String>) {
90    let boot = std::fs::read_to_string("/proc/sys/kernel/random/boot_id")
91        .ok()
92        .map(|text| text.trim().to_string())
93        .filter(|text| !text.is_empty());
94    (hostname(), boot)
95}
96
97fn hostname() -> Option<String> {
98    #[cfg(unix)]
99    {
100        let mut buffer = [0u8; 256];
101        // SAFETY: the buffer outlives the call and its length is passed.
102        let status = unsafe { libc::gethostname(buffer.as_mut_ptr().cast(), buffer.len()) };
103        if status != 0 {
104            return None;
105        }
106        let end = buffer
107            .iter()
108            .position(|byte| *byte == 0)
109            .unwrap_or(buffer.len());
110        String::from_utf8(buffer[..end].to_vec())
111            .ok()
112            .filter(|name| !name.is_empty())
113    }
114    #[cfg(not(unix))]
115    {
116        std::env::var("COMPUTERNAME").ok()
117    }
118}
119
120/// Why an operator verb could not do its work.
121#[derive(Debug, thiserror::Error)]
122pub enum OrchestratorError {
123    /// No lease file, or one that no longer names a live process.
124    #[error("the orchestrator is not running for `{0}` (no live lease at `{1}`)", root.display(), lock.display())]
125    NotRunning {
126        /// The home that was asked about.
127        root: PathBuf,
128        /// Where its lease would be.
129        lock: PathBuf,
130    },
131    /// A lease exists and its process is alive.
132    #[error("the orchestrator is already running for `{}` (pid {pid})", root.display())]
133    AlreadyRunning {
134        /// The home that was asked about.
135        root: PathBuf,
136        /// The live daemon's pid.
137        pid: u32,
138    },
139    /// The Node daemon entry could not be located.
140    #[error("no orchestrator daemon entry found (looked for `{DAEMON_ENTRY}` under: {searched})")]
141    NoDaemonEntry {
142        /// The candidate roots that were searched, joined.
143        searched: String,
144    },
145    /// The lease file could not be read or written.
146    #[error("orchestrator lease `{}`: {source}", path.display())]
147    Lease {
148        /// The lease path.
149        path: PathBuf,
150        /// The underlying I/O failure.
151        source: std::io::Error,
152    },
153    /// A service manager refused, or there is none on this platform.
154    #[error("orchestrator service: {action} failed: {detail}")]
155    Service {
156        /// What was attempted (`install`, `uninstall`).
157        action: &'static str,
158        /// What the service manager (or this module) said about it.
159        detail: String,
160    },
161}
162
163/// The lease path for one home.
164pub fn lock_path(root: &Path) -> PathBuf {
165    root.join(LOCK_FILE)
166}
167
168/// Read the lease, whether or not its process is still alive.
169pub fn read_lease(root: &Path) -> Option<Lease> {
170    let text = std::fs::read_to_string(lock_path(root)).ok()?;
171    serde_json::from_str(&text).ok()
172}
173
174/// Write the lease for a running daemon.
175pub fn write_lease(root: &Path, lease: &Lease) -> Result<(), OrchestratorError> {
176    let path = lock_path(root);
177    if let Some(parent) = path.parent() {
178        std::fs::create_dir_all(parent).map_err(|source| OrchestratorError::Lease {
179            path: path.clone(),
180            source,
181        })?;
182    }
183    let text = serde_json::to_string_pretty(lease).unwrap_or_default();
184    std::fs::write(&path, format!("{text}\n")).map_err(|source| OrchestratorError::Lease {
185        path: path.clone(),
186        source,
187    })
188}
189
190/// Remove the lease file. A missing file is not an error — `stop` is
191/// idempotent by design.
192pub fn clear_lease(root: &Path) -> Result<(), OrchestratorError> {
193    let path = lock_path(root);
194    match std::fs::remove_file(&path) {
195        Ok(()) => Ok(()),
196        Err(error) if error.kind() == std::io::ErrorKind::NotFound => Ok(()),
197        Err(source) => Err(OrchestratorError::Lease { path, source }),
198    }
199}
200
201/// Whether `pid` is a live process this user can signal.
202///
203/// `kill(pid, 0)` is the liveness question POSIX answers without touching the
204/// process. On a non-unix target there is no equivalent that does not start
205/// something, so the lease alone is the answer.
206pub fn pid_is_live(pid: u32) -> bool {
207    #[cfg(unix)]
208    {
209        if pid == 0 {
210            return false;
211        }
212        // SAFETY: signal 0 performs error checking only; it delivers nothing.
213        unsafe { libc::kill(pid as libc::pid_t, 0) == 0 }
214    }
215    #[cfg(not(unix))]
216    {
217        let _ = pid;
218        true
219    }
220}
221
222/// The lease of a daemon that is actually alive right now.
223pub fn live_lease(root: &Path) -> Option<Lease> {
224    read_lease(root).filter(Lease::is_live)
225}
226
227/// Ask the daemon to stop: SIGTERM to the leased pid, then clear the lease.
228///
229/// The daemon's own SIGTERM handler is what closes its adapters; this never
230/// escalates to SIGKILL and never signals anything but the pid the lease
231/// names.
232pub fn stop(root: &Path) -> Result<Lease, OrchestratorError> {
233    let Some(lease) = live_lease(root) else {
234        return Err(OrchestratorError::NotRunning {
235            root: root.to_path_buf(),
236            lock: lock_path(root),
237        });
238    };
239    #[cfg(unix)]
240    // SAFETY: the pid comes from this home's own lease and is known live.
241    unsafe {
242        libc::kill(lease.pid as libc::pid_t, libc::SIGTERM);
243    }
244    clear_lease(root)?;
245    Ok(lease)
246}
247
248/// Locate the Node daemon entry (`sdk/orchestrator/bin/orchestrator.mjs`).
249///
250/// Candidates, in order: `SUPERCODE_ORCHESTRATOR_ENTRY` (an explicit
251/// override, which is also how a test points at a fake), the published
252/// package's `supercode-orchestrator` command on PATH (what `npm install -g
253/// @volter/supercode-orchestrator` puts there), the repo checkout the
254/// running binary sits in, and the checkout it was built from. The current
255/// directory is never a candidate: the code a binary runs does not change
256/// with where it is run.
257pub fn daemon_entry() -> Result<PathBuf, OrchestratorError> {
258    let mut searched = Vec::new();
259    if let Some(explicit) = std::env::var_os("SUPERCODE_ORCHESTRATOR_ENTRY") {
260        let path = PathBuf::from(explicit);
261        if path.is_file() {
262            return Ok(path);
263        }
264        searched.push(path.display().to_string());
265    }
266    // An installed binary has no checkout beside it: the orchestrator is its
267    // own package, and npm links its daemon entry onto PATH by that name.
268    for dir in std::env::var_os("PATH")
269        .iter()
270        .flat_map(std::env::split_paths)
271    {
272        let command = dir.join("supercode-orchestrator");
273        if let Ok(entry) = std::fs::canonicalize(&command) {
274            if entry.is_file() && entry.ends_with(DAEMON_ENTRY) {
275                return Ok(entry);
276            }
277        }
278    }
279    searched.push("supercode-orchestrator on PATH".to_string());
280    let mut roots: Vec<PathBuf> = Vec::new();
281    if let Ok(exe) = std::env::current_exe() {
282        // target/<profile>/supercode → the workspace root is two levels up.
283        roots.extend(exe.ancestors().skip(1).take(4).map(Path::to_path_buf));
284    }
285    // A locally built binary's target directory can live anywhere (a shared
286    // cargo build dir, another volume), so the checkout it was built from is
287    // the last candidate. On an installed binary this path simply does not
288    // exist and is skipped like any other miss.
289    if let Some(workspace) = Path::new(env!("CARGO_MANIFEST_DIR")).ancestors().nth(2) {
290        roots.push(workspace.to_path_buf());
291    }
292    for root in roots {
293        let candidate = root.join("sdk/orchestrator").join(DAEMON_ENTRY);
294        if candidate.is_file() {
295            return Ok(candidate);
296        }
297        searched.push(candidate.display().to_string());
298    }
299    Err(OrchestratorError::NoDaemonEntry {
300        searched: searched.join(", "),
301    })
302}
303
304/// A rendered service unit: what `setup` prints and writes.
305#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
306pub struct ServiceUnit {
307    /// `launchd`, `systemd` or `schtasks`.
308    pub kind: &'static str,
309    /// Where `setup` writes the rendered text under `<home>/service/`.
310    pub path: PathBuf,
311    /// The unit text itself.
312    pub text: String,
313    /// The command an operator (or ORC-10) runs to install it.
314    pub install_command: String,
315    /// Files the unit reads, written beside it (a Windows task's environment file).
316    #[serde(skip)]
317    pub files: Vec<(PathBuf, String)>,
318}
319
320/// Render the per-platform service unit for one home.
321///
322/// Nothing is installed here: ORC-10 owns installation. `setup` writes this
323/// text under `<home>/service/` so the operator can read exactly what would
324/// be installed before anything registers a daemon.
325pub fn service_unit(root: &Path, entry: &Path, node: &str) -> ServiceUnit {
326    let root_display = root.display().to_string();
327    let entry_display = entry.display().to_string();
328    if cfg!(target_os = "macos") {
329        let path = root.join(SERVICE_DIR).join(format!("{SERVICE_NAME}.plist"));
330        let text = format!(
331            r#"<?xml version="1.0" encoding="UTF-8"?>
332<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
333<plist version="1.0">
334<dict>
335  <key>Label</key><string>{SERVICE_NAME}</string>
336  <key>ProgramArguments</key>
337  <array>
338    <string>{node}</string>
339    <string>{entry_display}</string>
340    <string>--root</string>
341    <string>{root_display}</string>
342  </array>
343  <key>RunAtLoad</key><true/>
344  <key>KeepAlive</key><true/>
345  <key>StandardOutPath</key><string>{root_display}/service/orchestrator.out.log</string>
346  <key>StandardErrorPath</key><string>{root_display}/service/orchestrator.err.log</string>
347</dict>
348</plist>
349"#
350        );
351        let install = format!("launchctl bootstrap gui/$(id -u) {}", path.display());
352        ServiceUnit {
353            kind: "launchd",
354            path,
355            text,
356            install_command: install,
357            files: Vec::new(),
358        }
359    } else {
360        let path = root
361            .join(SERVICE_DIR)
362            .join(format!("{SERVICE_NAME}.service"));
363        let text = format!(
364            "[Unit]\n\
365             Description=supercode orchestrator ({root_display})\n\
366             After=network.target\n\
367             \n\
368             [Service]\n\
369             ExecStart={node} {entry_display} --root {root_display}\n\
370             Restart=on-failure\n\
371             KillSignal=SIGTERM\n\
372             \n\
373             [Install]\n\
374             WantedBy=default.target\n"
375        );
376        let install = format!(
377            "systemctl --user link {} && systemctl --user enable --now {SERVICE_NAME}",
378            path.display()
379        );
380        ServiceUnit {
381            kind: "systemd",
382            path,
383            text,
384            install_command: install,
385            files: Vec::new(),
386        }
387    }
388}
389
390/// What the platform's service manager says about the orchestrator unit.
391#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
392pub struct ServiceState {
393    /// `launchd`, `systemd`, or `none` where neither is available.
394    pub kind: &'static str,
395    /// The label / unit name asked about.
396    pub label: String,
397    /// Whether the service manager holds the unit at all.
398    pub installed: bool,
399    /// The daemon's pid, where the service manager knows it.
400    pub pid: Option<u32>,
401    /// The manager's own words, or why the question could not be asked.
402    pub detail: String,
403}
404
405/// launchd and systemd start a unit with a minimal `PATH`, so the unit names
406/// the interpreter absolutely wherever one can be resolved.
407pub fn absolute_program(program: &str) -> String {
408    resolve_program(program).display().to_string()
409}
410
411/// `program` found on `PATH`, or `program` unchanged when it is a path or is not found. On Windows a bare name
412/// is also tried as `.exe`, `.cmd` and `.bat`: `Command::new("npm")` finds only `npm.exe`, and npm, Claude Code
413/// and Codex installed through npm are `.cmd` shims.
414pub fn resolve_program(program: &str) -> PathBuf {
415    resolve_program_in(
416        program,
417        std::env::var_os("PATH").as_deref(),
418        std::env::var("PATHEXT").ok().as_deref(),
419        cfg!(windows),
420    )
421}
422
423/// [`resolve_program`] over a given search path. On Windows only what PATHEXT names runs, in its order: npm
424/// installs an extensionless shell script beside each `.cmd` shim, and that script fails to start (os error
425/// 193). A name that already has an extension is taken as is.
426fn resolve_program_in(
427    program: &str,
428    path: Option<&std::ffi::OsStr>,
429    pathext: Option<&str>,
430    windows: bool,
431) -> PathBuf {
432    if program.contains('/') || (windows && program.contains('\\')) {
433        return PathBuf::from(program);
434    }
435    let extensions: Vec<String> = if windows && Path::new(program).extension().is_none() {
436        pathext
437            .unwrap_or(".COM;.EXE;.BAT;.CMD")
438            .split(';')
439            .filter(|extension| !extension.is_empty())
440            .map(str::to_ascii_lowercase)
441            .collect()
442    } else {
443        vec![String::new()]
444    };
445    for dir in path.map(std::env::split_paths).into_iter().flatten() {
446        for extension in &extensions {
447            let candidate = dir.join(format!("{program}{extension}"));
448            if candidate.is_file() {
449                return candidate;
450            }
451        }
452    }
453    PathBuf::from(program)
454}
455
456/// Run a service-manager command and return (success, stdout+stderr).
457fn run_tool(program: &str, args: &[&str]) -> Result<(bool, String), std::io::Error> {
458    let output = std::process::Command::new(program).args(args).output()?;
459    let mut text = String::from_utf8_lossy(&output.stdout).into_owned();
460    text.push_str(&String::from_utf8_lossy(&output.stderr));
461    Ok((output.status.success(), text.trim().to_string()))
462}
463
464#[cfg(target_os = "macos")]
465fn gui_domain() -> String {
466    // SAFETY: `getuid` reads this process's own real user id and cannot fail.
467    format!("gui/{}", unsafe { libc::getuid() })
468}
469
470/// Ask the platform's service manager about the orchestrator unit.
471///
472/// Never starts or installs anything: `status` calls this on every run.
473pub fn service_status(root: &Path) -> ServiceState {
474    platform_status(root)
475}
476
477#[cfg(target_os = "macos")]
478fn platform_status(_root: &Path) -> ServiceState {
479    let label = SERVICE_NAME.to_string();
480    let target = format!("{}/{SERVICE_NAME}", gui_domain());
481    match run_tool("launchctl", &["print", &target]) {
482        Ok((true, text)) => ServiceState {
483            kind: "launchd",
484            label,
485            installed: true,
486            pid: field_of(&text, "pid = ").and_then(|value| value.parse().ok()),
487            detail: field_of(&text, "state = ").unwrap_or_else(|| "loaded".into()),
488        },
489        Ok((false, _)) => ServiceState {
490            kind: "launchd",
491            label,
492            installed: false,
493            pid: None,
494            detail: format!("not bootstrapped in {}", gui_domain()),
495        },
496        Err(error) => ServiceState {
497            kind: "launchd",
498            label,
499            installed: false,
500            pid: None,
501            detail: format!("launchctl unavailable: {error}"),
502        },
503    }
504}
505
506#[cfg(all(unix, not(target_os = "macos")))]
507fn platform_status(_root: &Path) -> ServiceState {
508    let label = SERVICE_NAME.to_string();
509    match run_tool("systemctl", &["--user", "is-active", SERVICE_NAME]) {
510        Ok((active, text)) => {
511            let known = run_tool("systemctl", &["--user", "is-enabled", SERVICE_NAME])
512                .map(|(ok, _)| ok)
513                .unwrap_or(false);
514            ServiceState {
515                kind: "systemd",
516                label,
517                installed: active || known,
518                pid: None,
519                detail: if text.is_empty() {
520                    "unknown".into()
521                } else {
522                    text
523                },
524            }
525        }
526        Err(error) => ServiceState {
527            kind: "systemd",
528            label,
529            installed: false,
530            pid: None,
531            detail: format!("systemctl unavailable: {error}"),
532        },
533    }
534}
535
536#[cfg(not(unix))]
537fn platform_status(_root: &Path) -> ServiceState {
538    ServiceState {
539        kind: "none",
540        label: SERVICE_NAME.to_string(),
541        installed: false,
542        pid: None,
543        detail: "no service manager on this platform".into(),
544    }
545}
546
547/// `key = value` out of a service manager's block output.
548#[cfg(target_os = "macos")]
549fn field_of(text: &str, key: &str) -> Option<String> {
550    text.lines()
551        .find_map(|line| line.trim().strip_prefix(key))
552        .map(|value| value.trim().to_string())
553}
554
555/// Render the unit, hand it to the platform's service manager, and start it.
556///
557/// Refuses a label the manager already holds rather than replacing it: two
558/// homes share one label, so an install that silently took it over would point
559/// a running service at a different folder.
560pub fn install_service(
561    root: &Path,
562    entry: &Path,
563    node: &str,
564) -> Result<(ServiceUnit, ServiceState), OrchestratorError> {
565    let existing = service_status(root);
566    if existing.installed {
567        return Err(OrchestratorError::Service {
568            action: "install",
569            detail: format!(
570                "`{}` is already installed ({}); `supercode orchestrator setup --uninstall` first",
571                existing.label, existing.detail
572            ),
573        });
574    }
575    let unit = service_unit(root, entry, &absolute_program(node));
576    write_unit(&unit)?;
577    platform_install(&unit)?;
578    Ok((unit, service_status(root)))
579}
580
581#[cfg(target_os = "macos")]
582fn platform_install(unit: &ServiceUnit) -> Result<(), OrchestratorError> {
583    let path = unit.path.display().to_string();
584    let (ok, text) =
585        run_tool("launchctl", &["bootstrap", &gui_domain(), &path]).map_err(|error| {
586            OrchestratorError::Service {
587                action: "install",
588                detail: format!("launchctl: {error}"),
589            }
590        })?;
591    if !ok {
592        return Err(OrchestratorError::Service {
593            action: "install",
594            detail: format!("launchctl bootstrap {}: {text}", gui_domain()),
595        });
596    }
597    Ok(())
598}
599
600/// Untested on this box (the receipt is macOS); these are the commands
601/// `service_unit` has always printed as its `install_command`.
602#[cfg(all(unix, not(target_os = "macos")))]
603fn platform_install(unit: &ServiceUnit) -> Result<(), OrchestratorError> {
604    let path = unit.path.display().to_string();
605    for args in [
606        vec!["--user", "link", path.as_str()],
607        vec!["--user", "enable", "--now", SERVICE_NAME],
608    ] {
609        let (ok, text) =
610            run_tool("systemctl", &args).map_err(|error| OrchestratorError::Service {
611                action: "install",
612                detail: format!("systemctl: {error}"),
613            })?;
614        if !ok {
615            return Err(OrchestratorError::Service {
616                action: "install",
617                detail: format!("systemctl {}: {text}", args.join(" ")),
618            });
619        }
620    }
621    Ok(())
622}
623
624#[cfg(not(unix))]
625fn platform_install(_unit: &ServiceUnit) -> Result<(), OrchestratorError> {
626    Err(OrchestratorError::Service {
627        action: "install",
628        detail: "no service manager on this platform".into(),
629    })
630}
631
632/// Stop and unregister the unit, and remove the rendered file `setup` wrote.
633///
634/// Idempotent: a unit the manager does not hold is not an error, because the
635/// state the operator asked for is the state they get.
636pub fn uninstall_service(root: &Path) -> Result<ServiceState, OrchestratorError> {
637    platform_uninstall()?;
638    let unit_path = root.join(SERVICE_DIR).join(unit_file_name());
639    match std::fs::remove_file(&unit_path) {
640        Ok(()) => {}
641        Err(error) if error.kind() == std::io::ErrorKind::NotFound => {}
642        Err(source) => {
643            return Err(OrchestratorError::Lease {
644                path: unit_path,
645                source,
646            })
647        }
648    }
649    // `launchctl bootout` returns before the job is torn down, so the state
650    // this reports is the settled one, not the manager mid-teardown.
651    let mut state = service_status(root);
652    for _ in 0..40 {
653        if !state.installed {
654            break;
655        }
656        std::thread::sleep(std::time::Duration::from_millis(100));
657        state = service_status(root);
658    }
659    Ok(state)
660}
661
662#[cfg(target_os = "macos")]
663fn platform_uninstall() -> Result<(), OrchestratorError> {
664    let target = format!("{}/{SERVICE_NAME}", gui_domain());
665    let (ok, text) = run_tool("launchctl", &["bootout", &target]).map_err(|error| {
666        OrchestratorError::Service {
667            action: "uninstall",
668            detail: format!("launchctl: {error}"),
669        }
670    })?;
671    // `bootout` on a label nobody holds says so and exits non-zero.
672    if !ok && !text.contains("No such process") && !text.contains("not find") {
673        return Err(OrchestratorError::Service {
674            action: "uninstall",
675            detail: format!("launchctl bootout {target}: {text}"),
676        });
677    }
678    Ok(())
679}
680
681#[cfg(all(unix, not(target_os = "macos")))]
682fn platform_uninstall() -> Result<(), OrchestratorError> {
683    let _ = run_tool("systemctl", &["--user", "disable", "--now", SERVICE_NAME]);
684    Ok(())
685}
686
687#[cfg(not(unix))]
688fn platform_uninstall() -> Result<(), OrchestratorError> {
689    Ok(())
690}
691
692/// The file name `service_unit` renders for this platform.
693fn unit_file_name() -> String {
694    if cfg!(target_os = "macos") {
695        format!("{SERVICE_NAME}.plist")
696    } else {
697        format!("{SERVICE_NAME}.service")
698    }
699}
700
701/// Write a rendered unit under `<home>/service/`.
702pub fn write_unit(unit: &ServiceUnit) -> Result<(), OrchestratorError> {
703    if let Some(parent) = unit.path.parent() {
704        std::fs::create_dir_all(parent).map_err(|source| OrchestratorError::Lease {
705            path: unit.path.clone(),
706            source,
707        })?;
708    }
709    std::fs::write(&unit.path, &unit.text).map_err(|source| OrchestratorError::Lease {
710        path: unit.path.clone(),
711        source,
712    })
713}
714
715#[cfg(test)]
716mod tests {
717    use super::*;
718
719    /// A scratch home for one test, removed by the test that made it.
720    fn scratch(label: &str) -> PathBuf {
721        let root = std::env::temp_dir().join(format!(
722            "supercode-orchestrator-{label}-{}-{}",
723            std::process::id(),
724            std::time::SystemTime::now()
725                .duration_since(std::time::UNIX_EPOCH)
726                .unwrap()
727                .as_nanos()
728        ));
729        std::fs::create_dir_all(&root).unwrap();
730        root
731    }
732
733    #[test]
734    fn a_lease_round_trips_and_a_missing_one_is_not_running() {
735        let root = &scratch("lease");
736        let root = root.as_path();
737        assert!(read_lease(root).is_none());
738        assert!(live_lease(root).is_none());
739        let lease = Lease {
740            pid: std::process::id(),
741            started_at: "2026-09-04T00:00:00Z".into(),
742            root: root.to_path_buf(),
743            host: None,
744            boot: None,
745        };
746        write_lease(root, &lease).unwrap();
747        assert_eq!(read_lease(root).as_ref(), Some(&lease));
748        // This process is alive, so its own lease reads as live.
749        assert!(live_lease(root).is_some());
750        clear_lease(root).unwrap();
751        assert!(read_lease(root).is_none());
752        // `stop` on a home with no lease refuses by name.
753        assert!(matches!(
754            stop(root),
755            Err(OrchestratorError::NotRunning { .. })
756        ));
757        std::fs::remove_dir_all(root).ok();
758    }
759
760    /// A lease whose process is gone is STALE: `status` must say down, not
761    /// inherit the file's claim.
762    #[test]
763    fn a_stale_lease_is_not_live() {
764        let root = &scratch("stale");
765        let root = root.as_path();
766        write_lease(
767            root,
768            &Lease {
769                // A pid no process can hold (max_pid is far below this).
770                pid: 0x7FFF_FFFF,
771                started_at: "2026-09-04T00:00:00Z".into(),
772                root: root.to_path_buf(),
773                host: None,
774                boot: None,
775            },
776        )
777        .unwrap();
778        assert!(read_lease(root).is_some(), "the file is still there");
779        assert!(live_lease(root).is_none(), "but nothing is serving it");
780        std::fs::remove_dir_all(root).ok();
781    }
782
783    #[test]
784    fn the_service_unit_names_the_home_the_entry_and_its_install_command() {
785        let root = &scratch("unit");
786        let root = root.as_path();
787        let entry = PathBuf::from("/opt/supercode/sdk/orchestrator/bin/orchestrator.mjs");
788        let unit = service_unit(root, &entry, "/usr/bin/node");
789        assert!(unit.text.contains(&root.display().to_string()));
790        assert!(unit.text.contains("orchestrator.mjs"));
791        assert!(unit.text.contains(SERVICE_NAME));
792        assert!(unit
793            .install_command
794            .contains(&unit.path.display().to_string()));
795        assert!(unit.path.starts_with(root.join(SERVICE_DIR)));
796        assert_eq!(
797            unit.kind,
798            if cfg!(target_os = "macos") {
799                "launchd"
800            } else {
801                "systemd"
802            }
803        );
804        std::fs::remove_dir_all(root).ok();
805    }
806
807    /// Asking the service manager is a READ: `status` calls it on every run,
808    /// and must never register or render anything on the way.
809    #[test]
810    fn service_status_reports_the_label_and_installs_nothing() {
811        let root = &scratch("service-status");
812        let root = root.as_path();
813        let state = service_status(root);
814        assert_eq!(state.label, SERVICE_NAME);
815        assert!(
816            matches!(state.kind, "launchd" | "systemd" | "none"),
817            "{state:?}"
818        );
819        assert!(
820            !root.join(SERVICE_DIR).exists(),
821            "asking never writes a unit"
822        );
823        std::fs::remove_dir_all(root).ok();
824    }
825
826    /// launchd and systemd start a unit with a minimal PATH, so a bare program
827    /// name in the unit would not resolve at boot.
828    #[test]
829    fn a_program_is_resolved_absolutely_for_the_service_manager() {
830        assert_eq!(absolute_program("/usr/bin/env"), "/usr/bin/env");
831        let resolved = absolute_program("sh");
832        assert!(resolved.starts_with('/'), "{resolved}");
833        // an unresolvable name is left as it was, for the manager to refuse
834        assert_eq!(
835            absolute_program("definitely-not-a-program"),
836            "definitely-not-a-program"
837        );
838    }
839
840    /// The entry resolver must find the package in this checkout — the
841    /// `start` verb has nothing to spawn otherwise.
842    #[test]
843    fn the_daemon_entry_resolves_in_this_checkout() {
844        let entry = daemon_entry().expect("sdk/orchestrator/bin/orchestrator.mjs");
845        assert!(entry.ends_with(DAEMON_ENTRY));
846    }
847
848    #[test]
849    fn windows_programs_resolve_by_pathext_not_to_npms_shell_scripts() {
850        let dir = std::env::temp_dir().join(format!("supercode-resolve-{}", std::process::id()));
851        std::fs::create_dir_all(&dir).unwrap();
852        for file in ["npm", "npm.cmd", "tool.exe", "tool.cmd"] {
853            std::fs::write(dir.join(file), "").unwrap();
854        }
855        let path = Some(dir.as_os_str());
856        let pathext = Some(".COM;.EXE;.BAT;.CMD");
857        assert_eq!(
858            resolve_program_in("npm", path, pathext, true),
859            dir.join("npm.cmd")
860        );
861        assert_eq!(
862            resolve_program_in("tool", path, pathext, true),
863            dir.join("tool.exe")
864        );
865        assert_eq!(
866            resolve_program_in("npm.cmd", path, pathext, true),
867            dir.join("npm.cmd")
868        );
869        assert_eq!(
870            resolve_program_in("npm", path, pathext, false),
871            dir.join("npm")
872        );
873        assert_eq!(
874            resolve_program_in("absent", path, pathext, true),
875            PathBuf::from("absent")
876        );
877        std::fs::remove_dir_all(&dir).unwrap();
878    }
879}