1use chrono::{DateTime, Utc};
8use dpm_model::{
9 ActorId, LineageId, OperationId, RunEventId, RunId, RunState, ValidationError, WorkItemId,
10 WorkStatus,
11};
12use thiserror::Error;
13
14mod link;
15mod projection;
16mod source;
17mod start;
18mod transition;
19
20pub use link::{OperationFacts, check_link};
21pub use projection::{STALE_AFTER, project};
22pub use start::observe_start;
23pub use transition::{authorize, check_transition};
24
25#[derive(Debug, Error)]
27pub enum RunError {
28 #[error(transparent)]
30 Validation(#[from] ValidationError),
31 #[error("work item {0} does not exist")]
33 MissingWork(WorkItemId),
34 #[error("work item {0} is not a task and cannot be executed")]
36 NotATask(WorkItemId),
37 #[error("work item {work} is {status:?}; a run starts only on claimed or started work")]
39 NotExecuting {
40 work: WorkItemId,
42 status: WorkStatus,
44 },
45 #[error("work item {work} is owned by {}, not {executor}", owner.as_ref().map_or_else(|| "no one".to_string(), ToString::to_string))]
47 NotOwner {
48 work: WorkItemId,
50 executor: ActorId,
52 owner: Option<ActorId>,
54 },
55 #[error("actor {actor} may not {action} run {run} executed by {executor}")]
57 ActorNotAllowed {
58 actor: ActorId,
60 action: &'static str,
62 run: RunId,
64 executor: ActorId,
66 },
67 #[error("run {0} cannot be managed: only a service actor can record a managed run")]
69 ManagedNeedsService(RunId),
70 #[error("run source is not supported by the plan: {0}")]
72 UnsupportedSource(String),
73 #[error("run {run} cannot move from {from} to {to}")]
75 InvalidTransition {
76 run: RunId,
78 from: RunState,
80 to: RunState,
82 },
83 #[error("run {run} already ended as {state}; start a new run to try again")]
85 Terminal {
86 run: RunId,
88 state: RunState,
90 },
91 #[error("operation {operation} cannot be linked to run {run}: {reason}")]
93 LinkRefused {
94 run: RunId,
96 operation: OperationId,
98 #[source]
100 reason: LinkRefusal,
101 },
102}
103
104#[derive(Debug, Clone, Copy, PartialEq, Eq, Error)]
106pub enum LinkRefusal {
107 #[error("it belongs to another workspace")]
109 OtherWorkspace,
110 #[error("it was committed under another lineage than the run observed")]
112 OtherLineage,
113 #[error("it does not act on the run's task")]
115 OtherWork,
116 #[error("it was made by someone other than the run's executor")]
118 OtherActor,
119 #[error("it was committed before the run started")]
121 BeforeStart,
122 #[error("it was committed after the run ended")]
124 AfterEnd,
125}
126
127#[must_use]
129pub fn start_event(run: RunId) -> RunEventId {
130 RunEventId(run.0)
131}
132
133fn stale_after(receipt: DateTime<Utc>) -> Option<DateTime<Utc>> {
135 receipt.checked_add_signed(STALE_AFTER)
136}
137
138fn same_history(recorded: LineageId, current: Option<LineageId>) -> bool {
140 current == Some(recorded)
141}
142
143#[cfg(test)]
144mod tests;