Skip to main content

repolith_core/
action.rs

1use crate::types::{ActionId, BuildError, BuildOutput, Ctx, Sha256};
2use async_trait::async_trait;
3
4/// Represents an executable action within the repolith graph.
5///
6/// Actions are the fundamental units of work. They declare their dependencies,
7/// compute their input state, and execute their logic.
8///
9/// Note: Long-running `execute` implementations should periodically poll
10/// `ctx.cancel.is_cancelled()` to support graceful termination.
11#[async_trait]
12pub trait Action: Send + Sync {
13    /// Returns the unique identifier for this action.
14    fn id(&self) -> ActionId;
15
16    /// Returns the list of action IDs that this action depends on.
17    fn deps(&self) -> Vec<ActionId>;
18
19    /// Computes the hash representing the input state of this action.
20    /// This is used for caching and determining if the action needs to run.
21    async fn input_hash(&self, ctx: &Ctx) -> Result<Sha256, BuildError>;
22
23    /// Executes the action's core logic.
24    async fn execute(&self, ctx: &Ctx) -> Result<BuildOutput, BuildError>;
25
26    /// `true` for actions that only *coordinate* other actions (federation's
27    /// `RepolithSync`) rather than doing bounded work themselves.
28    ///
29    /// The orchestrator does **not** charge a concurrency permit for
30    /// coordinators: the child actions they spawn acquire from the same
31    /// shared semaphore, so charging the coordinator too would deadlock at
32    /// `--jobs 1` (the coordinator would hold the only permit while its
33    /// children wait for it).
34    fn is_coordinator(&self) -> bool {
35        false
36    }
37}
38
39#[cfg(test)]
40mod tests {
41    use super::*;
42
43    struct MockAction;
44
45    #[async_trait]
46    impl Action for MockAction {
47        fn id(&self) -> ActionId {
48            ActionId("mock".to_string())
49        }
50
51        fn deps(&self) -> Vec<ActionId> {
52            vec![]
53        }
54
55        async fn input_hash(&self, _ctx: &Ctx) -> Result<Sha256, BuildError> {
56            Ok(Sha256([0; 32]))
57        }
58
59        async fn execute(&self, _ctx: &Ctx) -> Result<BuildOutput, BuildError> {
60            Ok(BuildOutput {
61                output_hash: Sha256([1; 32]),
62                stdout: String::new(),
63            })
64        }
65    }
66
67    #[test]
68    fn test_action_is_object_safe() {
69        // This line will fail to compile if `Action` is not object-safe.
70        let _action: Box<dyn Action> = Box::new(MockAction);
71    }
72}