1use super::RunError;
4use chrono::{DateTime, Utc};
5use dpm_model::{
6 ActorId, ActorKind, ContractObservation, LineageId, Observation, Plan, RunRecord, RunStart,
7 WorkStatus,
8};
9
10pub fn observe_start(
17 plan: &Plan,
18 lineage: LineageId,
19 writer: &ActorId,
20 request: &RunStart,
21 now: DateTime<Utc>,
22) -> Result<RunRecord, RunError> {
23 request.validate()?;
24 super::transition::authorize_writer(writer, &request.executor, request.id, "start")?;
25 if request.observation == Observation::Managed && writer.kind != ActorKind::Service {
26 return Err(RunError::ManagedNeedsService(request.id));
27 }
28 let work = plan
29 .work_items
30 .get(&request.work)
31 .ok_or(RunError::MissingWork(request.work))?;
32 if !work.is_executable() {
33 return Err(RunError::NotATask(work.id));
34 }
35 if !matches!(
36 work.execution.status,
37 WorkStatus::Claimed | WorkStatus::InProgress
38 ) {
39 return Err(RunError::NotExecuting {
40 work: work.id,
41 status: work.execution.status,
42 });
43 }
44 if work.execution.owner.as_ref() != Some(&request.executor) {
45 return Err(RunError::NotOwner {
46 work: work.id,
47 executor: request.executor.clone(),
48 owner: work.execution.owner.clone(),
49 });
50 }
51 let exact_sources = request
52 .sources
53 .iter()
54 .map(|source| super::source::resolve(plan, work, source))
55 .collect::<Result<Vec<_>, _>>()?;
56 Ok(RunRecord {
57 id: request.id,
58 work: work.id,
59 executor: request.executor.clone(),
60 recorded_by: writer.clone(),
61 parent: request.parent,
62 session: request.session.clone(),
63 observation: request.observation,
64 sources: request.sources.clone(),
65 exact_sources,
66 contract: ContractObservation {
67 workspace_id: plan.workspace.id,
68 lineage_id: lineage,
69 revision: plan.revision,
70 work_key: work.key.clone(),
71 prior_submissions: u32::try_from(work.execution.attempts.len()).unwrap_or(u32::MAX),
72 contract: work.contract.clone(),
73 },
74 observed_started_at: request.observed_at,
75 started_at: now,
76 })
77}