evault_core/traits/process_runner.rs
1//! [`ProcessRunner`] — execute a child process with a custom environment.
2
3use std::collections::BTreeMap;
4use std::path::Path;
5
6use crate::error::RunnerError;
7
8/// Result of running a child process.
9#[derive(Debug, Clone, PartialEq, Eq)]
10pub struct ProcessOutcome {
11 /// Exit status reported by the OS. `None` if the process was terminated
12 /// by a signal (Unix).
13 pub exit_code: Option<i32>,
14}
15
16impl ProcessOutcome {
17 /// Returns `true` when the process exited normally with code `0`.
18 #[must_use]
19 pub const fn is_success(&self) -> bool {
20 matches!(self.exit_code, Some(0))
21 }
22}
23
24/// Spawn a child process with an injected environment.
25///
26/// The intent is `evault run --project X -- npm start`: the runner takes the
27/// resolved environment, spawns `npm start`, waits for it to exit, and
28/// propagates the exit code. The environment is **not** materialized to disk.
29pub trait ProcessRunner: Send + Sync {
30 /// Run `program` with the given `args`, working directory `cwd`, and an
31 /// extra environment overlay `env`.
32 ///
33 /// The overlay is **added to** (not replacing) the parent process's
34 /// environment so that `PATH` and similar variables remain available to
35 /// the child. Keys in `env` override identically-named keys from the
36 /// parent.
37 ///
38 /// **Implementors MUST** strip variables matching the `EVAULT_*` prefix
39 /// from the parent environment before applying the overlay, so that
40 /// internal runtime configuration (master key paths, telemetry knobs)
41 /// does not leak into untrusted child processes.
42 ///
43 /// **Implementors MUST** validate every supplied key (same shape as a
44 /// variable name) and reject values containing a NUL byte before
45 /// touching `std::process::Command` — a NUL would terminate the OS
46 /// environment block early and is non-recoverable.
47 ///
48 /// # Errors
49 /// Returns [`RunnerError::Invalid`] when the env overlay contains a
50 /// malformed key or a value with a NUL byte;
51 /// [`RunnerError::Spawn`] if the process could not start;
52 /// [`RunnerError::Io`] for IO failure during execution.
53 fn run(
54 &self,
55 program: &str,
56 args: &[String],
57 cwd: &Path,
58 env: &BTreeMap<String, String>,
59 ) -> Result<ProcessOutcome, RunnerError>;
60}