Skip to main content

supercode_harness/
harness_command.rs

1//! The controlled tier's substrate: one harness command, ready to run and
2//! ready to narrate.
3//!
4//! Every controlled-tier noun (ORCH-18 scheduled jobs, ORCH-21 profiles, …)
5//! mutates through the HARNESS'S OWN verb, executed as a subprocess. The three
6//! mechanics that are identical for every one of them live here so each noun
7//! implements only its own harness semantics:
8//!
9//! 1. **Narration.** [`HarnessCommand::narrate`] renders the exact argv that
10//!    ran, with every credential as `<redacted>` — tokens are never printed,
11//!    logged, or stored.
12//! 2. **Execution.** [`HarnessCommand::run`] returns the harness's stdout on
13//!    success and the harness's OWN stderr as the failure message, never a
14//!    supercode-invented sentence.
15//! 3. **Location.** [`harness_program`] finds the harness's executable from
16//!    the compiled registry, with a `SUPERCODE_<HARNESS>_BIN` override so a
17//!    fake CLI can stand in under test without touching PATH.
18
19use std::process::Command;
20
21/// Environment variable overriding the `hermes` executable (tests).
22pub const HERMES_BIN_ENV: &str = "SUPERCODE_HERMES_BIN";
23
24/// Test-only stand-in for the `SUPERCODE_*_BIN` override: thread-local, so a
25/// test that points one harness at a fake CLI cannot leak that fake into the
26/// sibling tests `cargo test` runs on other threads (process env is global).
27#[cfg(test)]
28thread_local! {
29    pub(crate) static TEST_PROGRAM_OVERRIDE: std::cell::RefCell<Option<(String, String)>> =
30        const { std::cell::RefCell::new(None) };
31}
32/// Environment variable overriding the `openclaw` executable (tests).
33pub const OPENCLAW_BIN_ENV: &str = "SUPERCODE_OPENCLAW_BIN";
34
35/// One argument of a harness command, tracking whether it is a secret.
36#[derive(Debug, Clone, PartialEq, Eq)]
37pub(crate) enum Arg {
38    Plain(String),
39    Secret,
40}
41
42/// A harness command, ready to run and ready to narrate.
43#[derive(Debug, Clone, PartialEq, Eq)]
44pub(crate) struct HarnessCommand {
45    pub(crate) program: String,
46    /// Rendered arguments; secrets are carried out of band.
47    pub(crate) args: Vec<Arg>,
48    /// The real value of each [`Arg::Secret`], in order.
49    pub(crate) secrets: Vec<String>,
50    pub(crate) env: Vec<(String, String)>,
51}
52
53impl HarnessCommand {
54    pub(crate) fn new(program: impl Into<String>) -> Self {
55        Self {
56            program: program.into(),
57            args: Vec::new(),
58            secrets: Vec::new(),
59            env: Vec::new(),
60        }
61    }
62
63    pub(crate) fn arg(&mut self, value: impl Into<String>) -> &mut Self {
64        self.args.push(Arg::Plain(value.into()));
65        self
66    }
67
68    pub(crate) fn args<I: IntoIterator<Item = S>, S: Into<String>>(
69        &mut self,
70        values: I,
71    ) -> &mut Self {
72        for value in values {
73            self.arg(value);
74        }
75        self
76    }
77
78    /// Push a credential: never rendered, never stored on the narration.
79    pub(crate) fn secret(&mut self, value: impl Into<String>) -> &mut Self {
80        self.args.push(Arg::Secret);
81        self.secrets.push(value.into());
82        self
83    }
84
85    pub(crate) fn env(&mut self, key: impl Into<String>, value: impl Into<String>) -> &mut Self {
86        self.env.push((key.into(), value.into()));
87        self
88    }
89
90    /// The narration: exactly what ran, with credentials as `<redacted>`.
91    pub(crate) fn narrate(&self) -> String {
92        let mut line = shell_quote(&self.program);
93        for arg in &self.args {
94            line.push(' ');
95            match arg {
96                Arg::Plain(value) => line.push_str(&shell_quote(value)),
97                Arg::Secret => line.push_str("<redacted>"),
98            }
99        }
100        line
101    }
102
103    /// Run it, returning stdout on success and a failure message carrying the
104    /// harness's own stderr otherwise.
105    pub(crate) fn run(&self) -> Result<String, String> {
106        let mut secrets = self.secrets.iter();
107        let mut command = Command::new(&self.program);
108        for arg in &self.args {
109            match arg {
110                Arg::Plain(value) => command.arg(value),
111                Arg::Secret => command.arg(secrets.next().expect("one secret per Arg::Secret")),
112            };
113        }
114        for (key, value) in &self.env {
115            command.env(key, value);
116        }
117        command.stdin(std::process::Stdio::null());
118        let output = command
119            .output()
120            .map_err(|error| format!("`{}` could not be executed: {error}", self.narrate()))?;
121        if output.status.success() {
122            return Ok(String::from_utf8_lossy(&output.stdout).into_owned());
123        }
124        let stderr = String::from_utf8_lossy(&output.stderr).trim().to_string();
125        let stdout = String::from_utf8_lossy(&output.stdout).trim().to_string();
126        let detail = if stderr.is_empty() { stdout } else { stderr };
127        Err(format!(
128            "`{}` failed ({}): {}",
129            self.narrate(),
130            output.status,
131            if detail.is_empty() {
132                "the harness printed nothing".to_string()
133            } else {
134                detail
135            }
136        ))
137    }
138}
139
140pub(crate) fn shell_quote(value: &str) -> String {
141    if !value.is_empty()
142        && value
143            .chars()
144            .all(|c| c.is_ascii_alphanumeric() || "-_./:@=+,".contains(c))
145    {
146        return value.to_string();
147    }
148    format!("'{}'", value.replace('\'', "'\\''"))
149}
150
151/// The harness's own executable.
152///
153/// The compiled registry names each harness's binary family in its runtime
154/// launch (`hermes-acp`, `openclaw`); the mutating verbs live on the base CLI,
155/// so an `-acp` bridge suffix is stripped. `SUPERCODE_HERMES_BIN` /
156/// `SUPERCODE_OPENCLAW_BIN` override it so a fake CLI can stand in under test
157/// without touching PATH.
158///
159/// `Err(None)` means the harness has no controlled-tier CLI at all, which each
160/// noun words in its own vocabulary; `Err(Some(message))` is a registry gap.
161pub(crate) fn harness_program(harness: &str) -> Result<String, Option<String>> {
162    #[cfg(test)]
163    if let Some(program) = TEST_PROGRAM_OVERRIDE.with(|slot| {
164        slot.borrow()
165            .as_ref()
166            .filter(|(id, _)| id == harness)
167            .map(|(_, program)| program.clone())
168    }) {
169        return Ok(program);
170    }
171    let variable = match harness {
172        crate::HarnessId::HERMES => HERMES_BIN_ENV,
173        crate::HarnessId::OPENCLAW => OPENCLAW_BIN_ENV,
174        _ => return Err(None),
175    };
176    if let Some(over) = std::env::var_os(variable) {
177        let over = over.to_string_lossy().trim().to_string();
178        if !over.is_empty() {
179            return Ok(over);
180        }
181    }
182    let registry = crate::harness_support_registry();
183    let program = registry
184        .harnesses
185        .iter()
186        .find(|descriptor| descriptor.id.as_str() == harness)
187        .and_then(|descriptor| descriptor.runtime.default_launch.as_ref())
188        .map(|launch| launch.program.clone())
189        .ok_or_else(|| {
190            Some(format!(
191                "the registry has no launch for `{harness}`, so its CLI cannot be located"
192            ))
193        })?;
194    Ok(program.strip_suffix("-acp").unwrap_or(&program).to_string())
195}