Skip to main content

sloop/runner/
mod.rs

1//! Execution boundary for one flow stage at a time.
2//!
3//! The daemon decides what runs and in what order; the runner only executes
4//! one stage order and returns factual evidence. Runners never merge, access
5//! durable storage directly, or derive verdicts and run outcomes.
6
7use std::ffi::OsString;
8use std::path::PathBuf;
9
10pub mod local;
11
12#[derive(Debug, Clone)]
13pub struct StageOrder {
14    pub run_id: String,
15    pub stage: String,
16    /// Which execution of `stage` this order is. A backward edge re-runs a
17    /// stage, so everything the execution produces — captured output above
18    /// all — is tagged with the attempt as well as the name.
19    pub attempt: u32,
20    pub execution: StageExecution,
21    pub worktree: PathBuf,
22    pub branch: String,
23    pub output_path: PathBuf,
24}
25
26#[derive(Debug, Clone)]
27pub enum StageExecution {
28    Agent(AgentLaunch),
29    Exec(ExecLaunch),
30}
31
32#[derive(Debug, Clone)]
33pub struct AgentLaunch {
34    pub argv: Vec<String>,
35    pub environment: Vec<(OsString, OsString)>,
36    /// The credentials this stage's agent presents on the worker socket. The
37    /// caller mints them so the daemon can serve them before the process
38    /// exists; the runner only hands them to the child.
39    pub worker: WorkerCredentials,
40}
41
42#[derive(Debug, Clone)]
43pub struct ExecLaunch {
44    pub argv: Vec<String>,
45    pub worker: Option<WorkerCredentials>,
46    /// Extra environment applied to the child. Empty for ordinary exec
47    /// stages; a spawned agent carries the agent environment here.
48    pub environment: Vec<(OsString, OsString)>,
49}
50
51#[derive(Debug, Clone, PartialEq, Eq)]
52pub struct WorkerCredentials {
53    pub socket: PathBuf,
54    pub token: String,
55    /// What the token authorises. Minted with the credential and never read
56    /// off a request, so a worker cannot widen its own authority by argument.
57    pub scope: WorkerScope,
58}
59
60/// The authority a worker token carries.
61#[derive(Debug, Clone, Default, PartialEq, Eq)]
62pub enum WorkerScope {
63    /// The stage's own worker. A verdict it reports lands on whichever stage
64    /// execution the driver has checkpointed a process for — there is exactly
65    /// one, so the answer is unambiguous without the token saying it.
66    #[default]
67    Stage,
68    /// One seat on a panel. A panel runs several workers against one stage
69    /// execution, so "the executing stage" no longer picks out a report row:
70    /// the seat is named by the credential instead, and a reviewer holding it
71    /// can report for nothing else — not another seat, not another attempt,
72    /// not another run.
73    PanelReviewer {
74        stage: String,
75        stage_index: usize,
76        attempt: u32,
77        reviewer_index: usize,
78    },
79}
80
81#[derive(Debug, Clone, Copy, PartialEq, Eq)]
82pub struct ProcessIdentity {
83    pub pid: u32,
84    pub start_time: Option<i64>,
85    pub process_group_id: u32,
86}
87
88#[derive(Debug, Clone, Copy, PartialEq, Eq)]
89pub struct ExecutionEvidence {
90    pub exit_code: Option<i32>,
91    pub started_at_ms: i64,
92    pub finished_at_ms: i64,
93    pub process: Option<ProcessIdentity>,
94    pub output_capture_complete: bool,
95    pub stragglers_killed: bool,
96}
97
98#[derive(Debug, Clone)]
99pub struct AgentProcessCheckpoint {
100    pub run_id: String,
101    pub stage: String,
102    pub attempt: u32,
103    pub branch: String,
104    pub worktree: PathBuf,
105    pub process: ProcessIdentity,
106    pub worker: WorkerCredentials,
107    pub started_at_ms: i64,
108}
109
110#[derive(Debug, Clone)]
111pub struct ExecProcessCheckpoint {
112    pub run_id: String,
113    pub stage: String,
114    pub attempt: u32,
115    pub process: ProcessIdentity,
116    pub started_at_ms: i64,
117}
118
119/// The only daemon-owned services available while a stage is executing.
120pub trait StageHooks {
121    type Error;
122
123    fn cancellation_requested(&self, run_id: &str) -> bool;
124
125    fn record_agent_process(&self, checkpoint: &AgentProcessCheckpoint) -> Result<(), Self::Error>;
126
127    fn record_exec_process(&self, checkpoint: &ExecProcessCheckpoint) -> Result<(), Self::Error>;
128}
129
130#[derive(Debug)]
131pub enum RunnerError<E> {
132    Execution(String),
133    Hook(E),
134}
135
136impl<E: std::fmt::Display> std::fmt::Display for RunnerError<E> {
137    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
138        match self {
139            Self::Execution(message) => formatter.write_str(message),
140            Self::Hook(error) => error.fmt(formatter),
141        }
142    }
143}
144
145#[derive(Debug)]
146pub struct ExecutionFailure<E> {
147    pub evidence: ExecutionEvidence,
148    pub error: RunnerError<E>,
149}