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-ai-dev/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` or `systemd`.
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}
316
317/// Render the per-platform service unit for one home.
318///
319/// Nothing is installed here: ORC-10 owns installation. `setup` writes this
320/// text under `<home>/service/` so the operator can read exactly what would
321/// be installed before anything registers a daemon.
322pub fn service_unit(root: &Path, entry: &Path, node: &str) -> ServiceUnit {
323    let root_display = root.display().to_string();
324    let entry_display = entry.display().to_string();
325    if cfg!(target_os = "macos") {
326        let path = root.join(SERVICE_DIR).join(format!("{SERVICE_NAME}.plist"));
327        let text = format!(
328            r#"<?xml version="1.0" encoding="UTF-8"?>
329<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
330<plist version="1.0">
331<dict>
332  <key>Label</key><string>{SERVICE_NAME}</string>
333  <key>ProgramArguments</key>
334  <array>
335    <string>{node}</string>
336    <string>{entry_display}</string>
337    <string>--root</string>
338    <string>{root_display}</string>
339  </array>
340  <key>RunAtLoad</key><true/>
341  <key>KeepAlive</key><true/>
342  <key>StandardOutPath</key><string>{root_display}/service/orchestrator.out.log</string>
343  <key>StandardErrorPath</key><string>{root_display}/service/orchestrator.err.log</string>
344</dict>
345</plist>
346"#
347        );
348        let install = format!("launchctl bootstrap gui/$(id -u) {}", path.display());
349        ServiceUnit {
350            kind: "launchd",
351            path,
352            text,
353            install_command: install,
354        }
355    } else {
356        let path = root
357            .join(SERVICE_DIR)
358            .join(format!("{SERVICE_NAME}.service"));
359        let text = format!(
360            "[Unit]\n\
361             Description=supercode orchestrator ({root_display})\n\
362             After=network.target\n\
363             \n\
364             [Service]\n\
365             ExecStart={node} {entry_display} --root {root_display}\n\
366             Restart=on-failure\n\
367             KillSignal=SIGTERM\n\
368             \n\
369             [Install]\n\
370             WantedBy=default.target\n"
371        );
372        let install = format!(
373            "systemctl --user link {} && systemctl --user enable --now {SERVICE_NAME}",
374            path.display()
375        );
376        ServiceUnit {
377            kind: "systemd",
378            path,
379            text,
380            install_command: install,
381        }
382    }
383}
384
385/// What the platform's service manager says about the orchestrator unit.
386#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
387pub struct ServiceState {
388    /// `launchd`, `systemd`, or `none` where neither is available.
389    pub kind: &'static str,
390    /// The label / unit name asked about.
391    pub label: String,
392    /// Whether the service manager holds the unit at all.
393    pub installed: bool,
394    /// The daemon's pid, where the service manager knows it.
395    pub pid: Option<u32>,
396    /// The manager's own words, or why the question could not be asked.
397    pub detail: String,
398}
399
400/// launchd and systemd start a unit with a minimal `PATH`, so the unit names
401/// the interpreter absolutely wherever one can be resolved.
402pub fn absolute_program(program: &str) -> String {
403    if program.contains('/') {
404        return program.to_string();
405    }
406    if let Some(path) = std::env::var_os("PATH") {
407        for dir in std::env::split_paths(&path) {
408            let candidate = dir.join(program);
409            if candidate.is_file() {
410                return candidate.display().to_string();
411            }
412        }
413    }
414    program.to_string()
415}
416
417/// Run a service-manager command and return (success, stdout+stderr).
418fn run_tool(program: &str, args: &[&str]) -> Result<(bool, String), std::io::Error> {
419    let output = std::process::Command::new(program).args(args).output()?;
420    let mut text = String::from_utf8_lossy(&output.stdout).into_owned();
421    text.push_str(&String::from_utf8_lossy(&output.stderr));
422    Ok((output.status.success(), text.trim().to_string()))
423}
424
425#[cfg(target_os = "macos")]
426fn gui_domain() -> String {
427    // SAFETY: `getuid` reads this process's own real user id and cannot fail.
428    format!("gui/{}", unsafe { libc::getuid() })
429}
430
431/// Ask the platform's service manager about the orchestrator unit.
432///
433/// Never starts or installs anything: `status` calls this on every run.
434pub fn service_status(root: &Path) -> ServiceState {
435    platform_status(root)
436}
437
438#[cfg(target_os = "macos")]
439fn platform_status(_root: &Path) -> ServiceState {
440    let label = SERVICE_NAME.to_string();
441    let target = format!("{}/{SERVICE_NAME}", gui_domain());
442    match run_tool("launchctl", &["print", &target]) {
443        Ok((true, text)) => ServiceState {
444            kind: "launchd",
445            label,
446            installed: true,
447            pid: field_of(&text, "pid = ").and_then(|value| value.parse().ok()),
448            detail: field_of(&text, "state = ").unwrap_or_else(|| "loaded".into()),
449        },
450        Ok((false, _)) => ServiceState {
451            kind: "launchd",
452            label,
453            installed: false,
454            pid: None,
455            detail: format!("not bootstrapped in {}", gui_domain()),
456        },
457        Err(error) => ServiceState {
458            kind: "launchd",
459            label,
460            installed: false,
461            pid: None,
462            detail: format!("launchctl unavailable: {error}"),
463        },
464    }
465}
466
467#[cfg(all(unix, not(target_os = "macos")))]
468fn platform_status(_root: &Path) -> ServiceState {
469    let label = SERVICE_NAME.to_string();
470    match run_tool("systemctl", &["--user", "is-active", SERVICE_NAME]) {
471        Ok((active, text)) => {
472            let known = run_tool("systemctl", &["--user", "is-enabled", SERVICE_NAME])
473                .map(|(ok, _)| ok)
474                .unwrap_or(false);
475            ServiceState {
476                kind: "systemd",
477                label,
478                installed: active || known,
479                pid: None,
480                detail: if text.is_empty() {
481                    "unknown".into()
482                } else {
483                    text
484                },
485            }
486        }
487        Err(error) => ServiceState {
488            kind: "systemd",
489            label,
490            installed: false,
491            pid: None,
492            detail: format!("systemctl unavailable: {error}"),
493        },
494    }
495}
496
497#[cfg(not(unix))]
498fn platform_status(_root: &Path) -> ServiceState {
499    ServiceState {
500        kind: "none",
501        label: SERVICE_NAME.to_string(),
502        installed: false,
503        pid: None,
504        detail: "no service manager on this platform".into(),
505    }
506}
507
508/// `key = value` out of a service manager's block output.
509#[cfg(target_os = "macos")]
510fn field_of(text: &str, key: &str) -> Option<String> {
511    text.lines()
512        .find_map(|line| line.trim().strip_prefix(key))
513        .map(|value| value.trim().to_string())
514}
515
516/// Render the unit, hand it to the platform's service manager, and start it.
517///
518/// Refuses a label the manager already holds rather than replacing it: two
519/// homes share one label, so an install that silently took it over would point
520/// a running service at a different folder.
521pub fn install_service(
522    root: &Path,
523    entry: &Path,
524    node: &str,
525) -> Result<(ServiceUnit, ServiceState), OrchestratorError> {
526    let existing = service_status(root);
527    if existing.installed {
528        return Err(OrchestratorError::Service {
529            action: "install",
530            detail: format!(
531                "`{}` is already installed ({}); `supercode orchestrator setup --uninstall` first",
532                existing.label, existing.detail
533            ),
534        });
535    }
536    let unit = service_unit(root, entry, &absolute_program(node));
537    write_unit(&unit)?;
538    platform_install(&unit)?;
539    Ok((unit, service_status(root)))
540}
541
542#[cfg(target_os = "macos")]
543fn platform_install(unit: &ServiceUnit) -> Result<(), OrchestratorError> {
544    let path = unit.path.display().to_string();
545    let (ok, text) =
546        run_tool("launchctl", &["bootstrap", &gui_domain(), &path]).map_err(|error| {
547            OrchestratorError::Service {
548                action: "install",
549                detail: format!("launchctl: {error}"),
550            }
551        })?;
552    if !ok {
553        return Err(OrchestratorError::Service {
554            action: "install",
555            detail: format!("launchctl bootstrap {}: {text}", gui_domain()),
556        });
557    }
558    Ok(())
559}
560
561/// Untested on this box (the receipt is macOS); these are the commands
562/// `service_unit` has always printed as its `install_command`.
563#[cfg(all(unix, not(target_os = "macos")))]
564fn platform_install(unit: &ServiceUnit) -> Result<(), OrchestratorError> {
565    let path = unit.path.display().to_string();
566    for args in [
567        vec!["--user", "link", path.as_str()],
568        vec!["--user", "enable", "--now", SERVICE_NAME],
569    ] {
570        let (ok, text) =
571            run_tool("systemctl", &args).map_err(|error| OrchestratorError::Service {
572                action: "install",
573                detail: format!("systemctl: {error}"),
574            })?;
575        if !ok {
576            return Err(OrchestratorError::Service {
577                action: "install",
578                detail: format!("systemctl {}: {text}", args.join(" ")),
579            });
580        }
581    }
582    Ok(())
583}
584
585#[cfg(not(unix))]
586fn platform_install(_unit: &ServiceUnit) -> Result<(), OrchestratorError> {
587    Err(OrchestratorError::Service {
588        action: "install",
589        detail: "no service manager on this platform".into(),
590    })
591}
592
593/// Stop and unregister the unit, and remove the rendered file `setup` wrote.
594///
595/// Idempotent: a unit the manager does not hold is not an error, because the
596/// state the operator asked for is the state they get.
597pub fn uninstall_service(root: &Path) -> Result<ServiceState, OrchestratorError> {
598    platform_uninstall()?;
599    let unit_path = root.join(SERVICE_DIR).join(unit_file_name());
600    match std::fs::remove_file(&unit_path) {
601        Ok(()) => {}
602        Err(error) if error.kind() == std::io::ErrorKind::NotFound => {}
603        Err(source) => {
604            return Err(OrchestratorError::Lease {
605                path: unit_path,
606                source,
607            })
608        }
609    }
610    // `launchctl bootout` returns before the job is torn down, so the state
611    // this reports is the settled one, not the manager mid-teardown.
612    let mut state = service_status(root);
613    for _ in 0..40 {
614        if !state.installed {
615            break;
616        }
617        std::thread::sleep(std::time::Duration::from_millis(100));
618        state = service_status(root);
619    }
620    Ok(state)
621}
622
623#[cfg(target_os = "macos")]
624fn platform_uninstall() -> Result<(), OrchestratorError> {
625    let target = format!("{}/{SERVICE_NAME}", gui_domain());
626    let (ok, text) = run_tool("launchctl", &["bootout", &target]).map_err(|error| {
627        OrchestratorError::Service {
628            action: "uninstall",
629            detail: format!("launchctl: {error}"),
630        }
631    })?;
632    // `bootout` on a label nobody holds says so and exits non-zero.
633    if !ok && !text.contains("No such process") && !text.contains("not find") {
634        return Err(OrchestratorError::Service {
635            action: "uninstall",
636            detail: format!("launchctl bootout {target}: {text}"),
637        });
638    }
639    Ok(())
640}
641
642#[cfg(all(unix, not(target_os = "macos")))]
643fn platform_uninstall() -> Result<(), OrchestratorError> {
644    let _ = run_tool("systemctl", &["--user", "disable", "--now", SERVICE_NAME]);
645    Ok(())
646}
647
648#[cfg(not(unix))]
649fn platform_uninstall() -> Result<(), OrchestratorError> {
650    Ok(())
651}
652
653/// The file name `service_unit` renders for this platform.
654fn unit_file_name() -> String {
655    if cfg!(target_os = "macos") {
656        format!("{SERVICE_NAME}.plist")
657    } else {
658        format!("{SERVICE_NAME}.service")
659    }
660}
661
662/// Write a rendered unit under `<home>/service/`.
663pub fn write_unit(unit: &ServiceUnit) -> Result<(), OrchestratorError> {
664    if let Some(parent) = unit.path.parent() {
665        std::fs::create_dir_all(parent).map_err(|source| OrchestratorError::Lease {
666            path: unit.path.clone(),
667            source,
668        })?;
669    }
670    std::fs::write(&unit.path, &unit.text).map_err(|source| OrchestratorError::Lease {
671        path: unit.path.clone(),
672        source,
673    })
674}
675
676#[cfg(test)]
677mod tests {
678    use super::*;
679
680    /// A scratch home for one test, removed by the test that made it.
681    fn scratch(label: &str) -> PathBuf {
682        let root = std::env::temp_dir().join(format!(
683            "supercode-orchestrator-{label}-{}-{}",
684            std::process::id(),
685            std::time::SystemTime::now()
686                .duration_since(std::time::UNIX_EPOCH)
687                .unwrap()
688                .as_nanos()
689        ));
690        std::fs::create_dir_all(&root).unwrap();
691        root
692    }
693
694    #[test]
695    fn a_lease_round_trips_and_a_missing_one_is_not_running() {
696        let root = &scratch("lease");
697        let root = root.as_path();
698        assert!(read_lease(root).is_none());
699        assert!(live_lease(root).is_none());
700        let lease = Lease {
701            pid: std::process::id(),
702            started_at: "2026-09-04T00:00:00Z".into(),
703            root: root.to_path_buf(),
704            host: None,
705            boot: None,
706        };
707        write_lease(root, &lease).unwrap();
708        assert_eq!(read_lease(root).as_ref(), Some(&lease));
709        // This process is alive, so its own lease reads as live.
710        assert!(live_lease(root).is_some());
711        clear_lease(root).unwrap();
712        assert!(read_lease(root).is_none());
713        // `stop` on a home with no lease refuses by name.
714        assert!(matches!(
715            stop(root),
716            Err(OrchestratorError::NotRunning { .. })
717        ));
718        std::fs::remove_dir_all(root).ok();
719    }
720
721    /// A lease whose process is gone is STALE: `status` must say down, not
722    /// inherit the file's claim.
723    #[test]
724    fn a_stale_lease_is_not_live() {
725        let root = &scratch("stale");
726        let root = root.as_path();
727        write_lease(
728            root,
729            &Lease {
730                // A pid no process can hold (max_pid is far below this).
731                pid: 0x7FFF_FFFF,
732                started_at: "2026-09-04T00:00:00Z".into(),
733                root: root.to_path_buf(),
734                host: None,
735                boot: None,
736            },
737        )
738        .unwrap();
739        assert!(read_lease(root).is_some(), "the file is still there");
740        assert!(live_lease(root).is_none(), "but nothing is serving it");
741        std::fs::remove_dir_all(root).ok();
742    }
743
744    #[test]
745    fn the_service_unit_names_the_home_the_entry_and_its_install_command() {
746        let root = &scratch("unit");
747        let root = root.as_path();
748        let entry = PathBuf::from("/opt/supercode/sdk/orchestrator/bin/orchestrator.mjs");
749        let unit = service_unit(root, &entry, "/usr/bin/node");
750        assert!(unit.text.contains(&root.display().to_string()));
751        assert!(unit.text.contains("orchestrator.mjs"));
752        assert!(unit.text.contains(SERVICE_NAME));
753        assert!(unit
754            .install_command
755            .contains(&unit.path.display().to_string()));
756        assert!(unit.path.starts_with(root.join(SERVICE_DIR)));
757        assert_eq!(
758            unit.kind,
759            if cfg!(target_os = "macos") {
760                "launchd"
761            } else {
762                "systemd"
763            }
764        );
765        std::fs::remove_dir_all(root).ok();
766    }
767
768    /// Asking the service manager is a READ: `status` calls it on every run,
769    /// and must never register or render anything on the way.
770    #[test]
771    fn service_status_reports_the_label_and_installs_nothing() {
772        let root = &scratch("service-status");
773        let root = root.as_path();
774        let state = service_status(root);
775        assert_eq!(state.label, SERVICE_NAME);
776        assert!(
777            matches!(state.kind, "launchd" | "systemd" | "none"),
778            "{state:?}"
779        );
780        assert!(
781            !root.join(SERVICE_DIR).exists(),
782            "asking never writes a unit"
783        );
784        std::fs::remove_dir_all(root).ok();
785    }
786
787    /// launchd and systemd start a unit with a minimal PATH, so a bare program
788    /// name in the unit would not resolve at boot.
789    #[test]
790    fn a_program_is_resolved_absolutely_for_the_service_manager() {
791        assert_eq!(absolute_program("/usr/bin/env"), "/usr/bin/env");
792        let resolved = absolute_program("sh");
793        assert!(resolved.starts_with('/'), "{resolved}");
794        // an unresolvable name is left as it was, for the manager to refuse
795        assert_eq!(
796            absolute_program("definitely-not-a-program"),
797            "definitely-not-a-program"
798        );
799    }
800
801    /// The entry resolver must find the package in this checkout — the
802    /// `start` verb has nothing to spawn otherwise.
803    #[test]
804    fn the_daemon_entry_resolves_in_this_checkout() {
805        let entry = daemon_entry().expect("sdk/orchestrator/bin/orchestrator.mjs");
806        assert!(entry.ends_with(DAEMON_ENTRY));
807    }
808}