tachyon_types/run.rs
1use std::collections::HashMap;
2
3use myko_macros::myko_item;
4use serde::{Deserialize, Serialize};
5use ts_rs::TS;
6
7use crate::PlaybookId;
8
9#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, TS)]
10#[serde(rename_all = "camelCase")]
11pub enum RunStatus {
12 /// Admitted to the queue but not yet started: waiting for a lease on every
13 /// resource it targets. Promoted to `Running` by the admission step.
14 Queued,
15 #[default]
16 Running,
17 Completed,
18 Failed,
19 Cancelled,
20}
21
22myko::register_ts_export!(RunStatus);
23
24#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, TS)]
25#[serde(rename_all = "camelCase")]
26pub struct HostStats {
27 pub ok: u32,
28 pub changed: u32,
29 pub failed: u32,
30 pub skipped: u32,
31 pub unreachable: u32,
32 #[serde(default, skip_serializing_if = "Option::is_none")]
33 pub last_failure: Option<HostFailure>,
34}
35
36myko::register_ts_export!(HostStats);
37
38#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
39#[serde(rename_all = "camelCase")]
40pub struct HostFailure {
41 pub task: String,
42 pub message: String,
43 pub seq: u64,
44}
45
46myko::register_ts_export!(HostFailure);
47
48#[myko_item]
49pub struct Run {
50 pub playbook_id: PlaybookId,
51 pub status: RunStatus,
52 pub started_at: String,
53 /// A cancellation request is deliberately separate from terminal status.
54 ///
55 /// A `Running` run keeps its lease while the runner interrupts, escalates,
56 /// and reaps the playbook process group. Only `FinishRun`, after that work
57 /// has completed, turns the run into terminal `Cancelled`. Older persisted
58 /// runs deserialize with no request.
59 #[serde(default, skip_serializing_if = "Option::is_none")]
60 pub cancel_requested_at: Option<String>,
61 #[serde(default, skip_serializing_if = "Option::is_none")]
62 pub finished_at: Option<String>,
63 #[serde(default, skip_serializing_if = "Option::is_none")]
64 pub exit_code: Option<i32>,
65 #[serde(default, skip_serializing_if = "Option::is_none")]
66 pub extra_vars: Option<serde_json::Value>,
67 #[serde(default, skip_serializing_if = "Option::is_none")]
68 pub limit: Option<String>,
69 /// Inventory groups this run targets (derived from `limit` at submission).
70 /// The lease queue serializes runs whose resources overlap. `#[serde(default)]`
71 /// keeps older persisted runs (written before the queue existed) loadable.
72 #[serde(default)]
73 pub resources: Vec<String>,
74 #[serde(default)]
75 pub host_stats: HashMap<String, HostStats>,
76 /// Identity of the WS connection that launched (or recorded) this run —
77 /// actor attribution (a human via the UI, an agent via MCP/CLI). Stamped by the
78 /// RunPlaybook/RecordRun handlers from the command's request context; the
79 /// `#[myko_client_id]` attribute only covers direct client SET events, which
80 /// never happen for runs (lv-67c8). `#[serde(default)]` keeps runs persisted
81 /// before this field existed loadable.
82 ///
83 /// NOTE: live only — the SQLite projection does not yet store this column, so
84 /// it resets to `None` on restart. Durable persistence (schema column +
85 /// insert/select) is a tracked follow-up.
86 #[myko_client_id]
87 #[serde(default, skip_serializing_if = "Option::is_none")]
88 pub client_id: Option<String>,
89 /// The runner that holds this run for execution — set by `ClaimRun` (a CAS:
90 /// succeeds only while unclaimed), never by the runner watching alone. The
91 /// execution gate: a run is executed by exactly the runner that won the claim,
92 /// so a second runner cannot double-execute and a restarted runner does not
93 /// re-grab work it already spawned. `#[serde(default)]` keeps pre-claim runs
94 /// loadable; a terminal record never carries a claim.
95 #[serde(default, skip_serializing_if = "Option::is_none")]
96 pub claimed_by: Option<String>,
97}