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, PartialEq, Eq)]
62pub enum WorkerScope {
63    /// The stage's own worker, named by the credential the same way a panel
64    /// seat is. The stage cannot be read back off the checkpointed process
65    /// instead: the driver clears the `stage_process` row when a stage ends and
66    /// writes the next one only after that stage's child is already spawned, so
67    /// between those two points there is no row to read. A worker that reported
68    /// inside the window was refused outright, and the check it was answering
69    /// never saw the report it was waiting for.
70    Stage { stage: String, attempt: u32 },
71    /// One seat on a panel. A panel runs several workers against one stage
72    /// execution, so "the executing stage" no longer picks out a report row:
73    /// the seat is named by the credential instead, and a reviewer holding it
74    /// can report for nothing else — not another seat, not another attempt,
75    /// not another run.
76    PanelReviewer {
77        stage: String,
78        stage_index: usize,
79        attempt: u32,
80        reviewer_index: usize,
81    },
82}
83
84#[derive(Debug, Clone, Copy, PartialEq, Eq)]
85pub struct ProcessIdentity {
86    pub pid: u32,
87    pub start_time: Option<i64>,
88    pub process_group_id: u32,
89}
90
91#[derive(Debug, Clone, Copy, PartialEq, Eq)]
92pub struct ExecutionEvidence {
93    pub exit_code: Option<i32>,
94    pub started_at_ms: i64,
95    pub finished_at_ms: i64,
96    pub process: Option<ProcessIdentity>,
97    pub output_capture_complete: bool,
98    pub stragglers_killed: bool,
99}
100
101#[derive(Debug, Clone)]
102pub struct AgentProcessCheckpoint {
103    pub run_id: String,
104    pub stage: String,
105    pub attempt: u32,
106    pub branch: String,
107    pub worktree: PathBuf,
108    pub process: ProcessIdentity,
109    pub worker: WorkerCredentials,
110    pub started_at_ms: i64,
111}
112
113#[derive(Debug, Clone)]
114pub struct ExecProcessCheckpoint {
115    pub run_id: String,
116    pub stage: String,
117    pub attempt: u32,
118    pub process: ProcessIdentity,
119    pub started_at_ms: i64,
120}
121
122/// The only daemon-owned services available while a stage is executing.
123pub trait StageHooks {
124    type Error;
125
126    fn cancellation_requested(&self, run_id: &str) -> bool;
127
128    fn record_agent_process(&self, checkpoint: &AgentProcessCheckpoint) -> Result<(), Self::Error>;
129
130    fn record_exec_process(&self, checkpoint: &ExecProcessCheckpoint) -> Result<(), Self::Error>;
131}
132
133#[derive(Debug)]
134pub enum RunnerError<E> {
135    Execution(String),
136    Hook(E),
137}
138
139impl<E: std::fmt::Display> std::fmt::Display for RunnerError<E> {
140    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
141        match self {
142            Self::Execution(message) => formatter.write_str(message),
143            Self::Hook(error) => error.fmt(formatter),
144        }
145    }
146}
147
148#[derive(Debug)]
149pub struct ExecutionFailure<E> {
150    pub evidence: ExecutionEvidence,
151    pub error: RunnerError<E>,
152}