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/// Environment variable overriding the `openclaw` executable (tests).
25pub const OPENCLAW_BIN_ENV: &str = "SUPERCODE_OPENCLAW_BIN";
26
27/// One argument of a harness command, tracking whether it is a secret.
28#[derive(Debug, Clone, PartialEq, Eq)]
29pub(crate) enum Arg {
30    Plain(String),
31    Secret,
32}
33
34/// A harness command, ready to run and ready to narrate.
35#[derive(Debug, Clone, PartialEq, Eq)]
36pub(crate) struct HarnessCommand {
37    pub(crate) program: String,
38    /// Rendered arguments; secrets are carried out of band.
39    pub(crate) args: Vec<Arg>,
40    /// The real value of each [`Arg::Secret`], in order.
41    pub(crate) secrets: Vec<String>,
42    pub(crate) env: Vec<(String, String)>,
43}
44
45impl HarnessCommand {
46    pub(crate) fn new(program: impl Into<String>) -> Self {
47        Self {
48            program: program.into(),
49            args: Vec::new(),
50            secrets: Vec::new(),
51            env: Vec::new(),
52        }
53    }
54
55    pub(crate) fn arg(&mut self, value: impl Into<String>) -> &mut Self {
56        self.args.push(Arg::Plain(value.into()));
57        self
58    }
59
60    pub(crate) fn args<I: IntoIterator<Item = S>, S: Into<String>>(
61        &mut self,
62        values: I,
63    ) -> &mut Self {
64        for value in values {
65            self.arg(value);
66        }
67        self
68    }
69
70    /// Push a credential: never rendered, never stored on the narration.
71    pub(crate) fn secret(&mut self, value: impl Into<String>) -> &mut Self {
72        self.args.push(Arg::Secret);
73        self.secrets.push(value.into());
74        self
75    }
76
77    pub(crate) fn env(&mut self, key: impl Into<String>, value: impl Into<String>) -> &mut Self {
78        self.env.push((key.into(), value.into()));
79        self
80    }
81
82    /// The narration: exactly what ran, with credentials as `<redacted>`.
83    pub(crate) fn narrate(&self) -> String {
84        let mut line = shell_quote(&self.program);
85        for arg in &self.args {
86            line.push(' ');
87            match arg {
88                Arg::Plain(value) => line.push_str(&shell_quote(value)),
89                Arg::Secret => line.push_str("<redacted>"),
90            }
91        }
92        line
93    }
94
95    /// Run it, returning stdout on success and a failure message carrying the
96    /// harness's own stderr otherwise.
97    pub(crate) fn run(&self) -> Result<String, String> {
98        let mut secrets = self.secrets.iter();
99        let mut command = Command::new(&self.program);
100        for arg in &self.args {
101            match arg {
102                Arg::Plain(value) => command.arg(value),
103                Arg::Secret => command.arg(secrets.next().expect("one secret per Arg::Secret")),
104            };
105        }
106        for (key, value) in &self.env {
107            command.env(key, value);
108        }
109        command.stdin(std::process::Stdio::null());
110        let output = command
111            .output()
112            .map_err(|error| format!("`{}` could not be executed: {error}", self.narrate()))?;
113        if output.status.success() {
114            return Ok(String::from_utf8_lossy(&output.stdout).into_owned());
115        }
116        let stderr = String::from_utf8_lossy(&output.stderr).trim().to_string();
117        let stdout = String::from_utf8_lossy(&output.stdout).trim().to_string();
118        let detail = if stderr.is_empty() { stdout } else { stderr };
119        Err(format!(
120            "`{}` failed ({}): {}",
121            self.narrate(),
122            output.status,
123            if detail.is_empty() {
124                "the harness printed nothing".to_string()
125            } else {
126                detail
127            }
128        ))
129    }
130}
131
132pub(crate) fn shell_quote(value: &str) -> String {
133    if !value.is_empty()
134        && value
135            .chars()
136            .all(|c| c.is_ascii_alphanumeric() || "-_./:@=+,".contains(c))
137    {
138        return value.to_string();
139    }
140    format!("'{}'", value.replace('\'', "'\\''"))
141}
142
143/// The harness's own executable.
144///
145/// The compiled registry names each harness's binary family in its runtime
146/// launch (`hermes-acp`, `openclaw`); the mutating verbs live on the base CLI,
147/// so an `-acp` bridge suffix is stripped. `SUPERCODE_HERMES_BIN` /
148/// `SUPERCODE_OPENCLAW_BIN` override it so a fake CLI can stand in under test
149/// without touching PATH.
150///
151/// `Err(None)` means the harness has no controlled-tier CLI at all, which each
152/// noun words in its own vocabulary; `Err(Some(message))` is a registry gap.
153pub(crate) fn harness_program(harness: &str) -> Result<String, Option<String>> {
154    let variable = match harness {
155        crate::HarnessId::HERMES => HERMES_BIN_ENV,
156        crate::HarnessId::OPENCLAW => OPENCLAW_BIN_ENV,
157        _ => return Err(None),
158    };
159    if let Some(over) = std::env::var_os(variable) {
160        let over = over.to_string_lossy().trim().to_string();
161        if !over.is_empty() {
162            return Ok(over);
163        }
164    }
165    let registry = crate::harness_support_registry();
166    let program = registry
167        .harnesses
168        .iter()
169        .find(|descriptor| descriptor.id.as_str() == harness)
170        .and_then(|descriptor| descriptor.runtime.default_launch.as_ref())
171        .map(|launch| launch.program.clone())
172        .ok_or_else(|| {
173            Some(format!(
174                "the registry has no launch for `{harness}`, so its CLI cannot be located"
175            ))
176        })?;
177    Ok(program.strip_suffix("-acp").unwrap_or(&program).to_string())
178}