Skip to main content

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    #[serde(default, skip_serializing_if = "Option::is_none")]
54    pub finished_at: Option<String>,
55    #[serde(default, skip_serializing_if = "Option::is_none")]
56    pub exit_code: Option<i32>,
57    #[serde(default, skip_serializing_if = "Option::is_none")]
58    pub extra_vars: Option<serde_json::Value>,
59    #[serde(default, skip_serializing_if = "Option::is_none")]
60    pub limit: Option<String>,
61    /// Inventory groups this run targets (derived from `limit` at submission).
62    /// The lease queue serializes runs whose resources overlap. `#[serde(default)]`
63    /// keeps older persisted runs (written before the queue existed) loadable.
64    #[serde(default)]
65    pub resources: Vec<String>,
66    #[serde(default)]
67    pub host_stats: HashMap<String, HostStats>,
68    /// Identity of the WS connection that launched (or recorded) this run —
69    /// actor attribution (a human via the UI, an agent via MCP/CLI). Stamped by the
70    /// RunPlaybook/RecordRun handlers from the command's request context; the
71    /// `#[myko_client_id]` attribute only covers direct client SET events, which
72    /// never happen for runs (lv-67c8). `#[serde(default)]` keeps runs persisted
73    /// before this field existed loadable.
74    ///
75    /// NOTE: live only — the SQLite projection does not yet store this column, so
76    /// it resets to `None` on restart. Durable persistence (schema column +
77    /// insert/select) is a tracked follow-up.
78    #[myko_client_id]
79    #[serde(default, skip_serializing_if = "Option::is_none")]
80    pub client_id: Option<String>,
81    /// The runner that holds this run for execution — set by `ClaimRun` (a CAS:
82    /// succeeds only while unclaimed), never by the runner watching alone. The
83    /// execution gate: a run is executed by exactly the runner that won the claim,
84    /// so a second runner cannot double-execute and a restarted runner does not
85    /// re-grab work it already spawned. `#[serde(default)]` keeps pre-claim runs
86    /// loadable; a terminal record never carries a claim.
87    #[serde(default, skip_serializing_if = "Option::is_none")]
88    pub claimed_by: Option<String>,
89}